论坛首页 入门技术论坛

关于Java实例化的本质

浏览 12514 次
该帖已经被评为新手帖
作者 正文
   发表时间:2010-04-23   最后修改:2010-04-23
这有啥好理解的 = =!
LZ可以读一下深入java虚拟机第二版第七章。。。
0 请登录后投票
   发表时间:2010-04-23  
jameswxx 写道

4:对象的初始化顺序
对象的初始化都先从父类开始,顺序如下:
给父类静态变量默认值
对父类静态变量赋值
执行父类静态块

给当前类静态变量默认值
对当前类静态变量赋值
执行当前类静态块

给父类变量默认值
对父类变量赋值
执行父类构造函数

给当前类变量默认值
对当前类变量赋值
执行当前类构造函数

这个里头有几个顺序是绝对的,比如肯定是先执行静态块,在执行构造函数,肯定是先抽取变量,在执行构造函数。但是是不是一定要:
给父类变量默认值
对父类变量赋值
执行父类构造函数

给当前类变量默认值
对当前类变量赋值
执行当前类构造函数

为什么不是:
给父类变量默认值
对父类变量赋值
给当前类变量默认值
对当前类变量赋值
执行父类构造函数
执行当前类构造函数

?求解释
0 请登录后投票
   发表时间:2010-04-23  
20055294 写道
深入Java虚拟机可以 读读

看一遍,一头雾水啊 看来我太菜了
0 请登录后投票
   发表时间:2010-04-23  
LZ说的太浅显了
0 请登录后投票
   发表时间:2010-04-23  
建议这个问题去看看 深入Java虚拟机
0 请登录后投票
   发表时间:2010-04-23  
zhxing 写道
Java 中所有对象都是Object 的子类。。要实例化子类,必须先实例化了父类。。这个是肯定的,lz这样说好像没什么意思。。。
本质应该看JVM的实现。


不会实例化父类,但会默认调用父类无参构造方法
0 请登录后投票
   发表时间:2010-04-23  
本质是通过实例使用类。情况可以会比较复杂,因为有继承,多态,等情况。而且,实例是以哪种形式存在的,常驻,还是暂时的,也就是生命周期。
0 请登录后投票
   发表时间:2010-04-23   最后修改:2010-04-23
//有点长,不想看下面的的就去看楼上的仁兄说的,深入JVM那本书.

String string = new String(); //

Jvm对象的生成,首先要保证此对象的类型已被正确初始化.
首先了解下类型初始化:也就是在JVM运行时数据区已存在此类型的类型数据信息,并且已被初始化,只有已经被正确初始化的类型JVM才会提供用来生成实例对象,类型初始化是实现类型可用的步骤之一.在此步骤之前还有两个步骤:装载,连接,
连接又分为三个子步骤:检查,准备,解析.
装载就是类装载器查找一个class文件并将此class文件中的类型信息解释为运行时数据区中方法区的类型信息,装载当前的类型会导致当前类型的所有超类型的装载(如果超类型已装载,则不会被再次装载),直到java.lang.Object,装载步骤顺利完成的标志是在运行时数据区的堆区创建一个指向此类型信息的Class对象,如果装载过程中没有找到期望的.class文件,
或者找到了一个.class文件,但检查此.class文件是不符合class文件规范的以至于不能被正常的解释为方法区的类型信息数据,或者违反了二进制兼容性,JVM都会抛出异常.
正确装载完毕,就可以进行下一步连接了,连接的第一步是:检查,所有的检查步骤都通过,进行连接的第二步:准备,就是给当前类型的所有类变量赋予类型的默认值,这也就是为什么类变量只有声明语句而未被初始化它还有值的原因,连接的第二步完成后,进入第三步:解析,
就是将类型信息所保存的符号引用解析为直接引用,这一步的时机比较特殊,原因在于Java是动态连接的,解析步骤的时机可能发生在类型初始化之后,并且解析可能会引起其它的类型的加载. 连接的第三步:解析之后, 进行类型的初始化步骤,将类变量赋予正确的初始值,也就是static int i = 4;给类变量i赋予初始值4. 类的初始化需要特定的时机,类的初始化操作会把所有的类变量初始化语句和静态初始化代码块集中到一个<clinit>()的方法中,这个方法不会执行超类型的<clinit>(),但它要保证超类型已执行过自己的<clinit>()方法.
这些步骤都完成后,类的初始化就完成了,此类型也就可以被用来生成对象了.
JVM首先根据类型信息在堆上分配对象的初始内存空间,所有的实例变量都被赋予类型的默认值,包括此类型的所有超类型的实例变量,这个步骤结束后,就进入对象的真正的初始化阶段,初始化从超类开始,超类初始化结束后进行子类初始化.初始化步骤JVM会为每个对象的构造方法生成一个<init>()方法,这个方法可能包含三种代码,一种是对同一类中其它<init>()方法的调用,第二是对实例变量的初始化代码和实例初始化代码块,第三种是构造方法内的代码。 实际的初始化代码分两种情况: 一种是构造方法内部以另一个构造方法开始, <init>()方法包含另一个构造方法的代码和自己构造方法的代码.
另一种是构造方法以超类构造方法开始. <init>()方法包含超类的代码执行,实例变量初始化语句和实例初始化代码块的代码,还有就是自己构造方法的代码.这里的例外是类根java.lang.Object.
以上这些步骤都完成后,这个对象实例也就被完全的创建出来了.

对象使用完了之后,内存空间的释放交由垃圾回收器处理.当一个类型的所有对象都不会被程序中用到,方法区中的类型信息所占用的内存空间也符合被释放的条件,一旦类型被卸载,这个类型的生命周期也走到了尽头.
对象的生命周期是类型生命周期的一部分,JVM就是一个容器,来管理其中的类型信息和对象实例,这些是它的任务之一,还有其它任务。



可能有用的名词:A,B,C,D,E,F,G.
A.造成不符合规范的class文件的可能原因:
1.有缺陷的虚拟机生成的.class文件.
2.高版本的.class在低版本的JVM上运行,
3.恶意的程序员直接生成的字节流,

B.双亲委派模型:
所有的类装载器都有父装载器,除了启动类装载器.自己定义的类装载器可以显示指明它的父类装载器,不指明则父装载器为启动类装载器.一个类装载器会首先让父类装载器装载当前类型,一级一级向上委派直到启动类装载器,其中有一个父类装载器可以装载就返回装载后的类型,如果所有父类装载器装载失败,当前类装载器就去装载此类型. 这个操作主要用于安全目的,启动类装器先将API中的类装载,运行时所有API的类都已被加载,你如果使用一个和API中相同名字的类将不会被再次加载,而是将直接返回已加载过的类型,
再结合运行时包的保护,使得同一个包下的类在运行时由同一个类装载器装载,彼此之间才能互相访问,比如你想装载一个java.lang.Virus将不会得到访问java.lang.*包下的其它类的访问权限.
这两种攻击方式一种是替换一个原有的类型,另一种是新加入一个类型都将会失败.

C.class文件检查器的检查步骤:分四步:
第一步针对.class文件结构检查,发生在类装载器装载期间.
第二步,第三步,第四步都是在方法区的类型信息数据上进行的检查,第二步,第三步是在连接期检查,第四步可能是在连接期,也可能是在初始化之后的运行时,
第二步针对语法进行检查,
第三步针对字节码检查,
第四步检查所依赖的类是否兼容,比如:类A依赖于类B,类A中调用b.play()方法,此进要检查类B中是否存在play()方法,检查依据看它们方法签名是否一执返回值是否相同,如果是父子关系在Java5后支持协变返回类型.

D.二进制兼容性:
当一个类改动后,要保证依赖于它的类不会出错.

E.动态连接:
运行时装载新类型的能力.

F.符号引用:
因为是运态连接,.class文件中和方法区中的类型信息未执行解析步骤前,对其它类型都是以符号引用的方式保持着关系.
它是一种特殊的字符串, 包含三个信息:1.另一个类型的完整限定名,2.字段名或方法名,3.描述符.
描述符有字段描述符和方法描述符.
一个符号引用必需能确定它所引用的字段或方法.

G.类型的默认值:
数值类型的零或与零相当的值,布尔的false值,所有引用类型的null值,目的在于即使一个类变量或实例变量仅被声明而未被正确初始化,也能保证它是有值的,不像C那样,你不进行初始化,就随便搞个值给你,问题多多. 
本地变量不进行初始化,编译器不会通过.
正确的初始值:
就是程序当中所赋的初始值.

PS:详细内容就像楼上的仁兄说的,看看深入JVM那本书.
0 请登录后投票
   发表时间:2010-04-23   最后修改:2010-04-23
这是个基础问题,如果类有父类,当调用子类构造函数进行实例化时,肯定是执行父类构造函数,而非实例化父类,这对于c++、c#都是相同的。如果先执行子类的构造函数,岂不是要把子类的初始化可能被父类覆盖掉。
0 请登录后投票
   发表时间:2010-04-24  
jameswxx 写道
当new一个对象的时候发生了什么?我就给大家讲讲吧,如果不对的地方,还请大家指正
如new MyObject();

1:寻找类定义
jvm会在自己的一个名叫“方法区”的内存块中,寻找名叫“MyObject”的Class对象(注意class也是一个对象,该对象记录了所有类的定义),如果有,则按照Class对象的定义,生成一个MyObject对象。

2:加载类定义
如果“方法区”中没有名为“MyObject”的Class对象,jvm会用当前类的类加载器(classloader)从当前的classpath路径寻找名为"MyObject.class"的文件,如果找到,则将文件进行分析,转换为Class对象存放在“方法区”中,否则抛出“ClassNotFoundException”。对于jdk的class,jvm启动时,会用启动类加载器加载,对于用户的class,则会用应用程序类加载器实时加载,所谓实时加载,指的是遇到的时候再加载,而不是预先一次性加载。关于类加载器,有三级,jvm严格的限制了每一级的加载权限,加载模式为“双亲委托模式”,加载任何类,都先由父加载器加载。


3:给对象分配内存空间
找到MyObject的类定义后,jvm在内存“堆”中,开辟一个空间,该空间按照MyObject类定义开辟,并将该空间中的各个内存段设置默认值,对应的就是对象的属性初始化默认值。


4:对象的初始化顺序
对象的初始化都先从父类开始,顺序如下:
给父类静态变量默认值
对父类静态变量赋值
执行父类静态块

给当前类静态变量默认值
对当前类静态变量赋值
执行当前类静态块

给父类变量默认值
对父类变量赋值
执行父类构造函数

给当前类变量默认值
对当前类变量赋值
执行当前类构造函数

5:对象构造完成



还有一点要提醒楼主,当你new一个String的时候,只是生成一个String对象,而没有生成Object对象,Object的类定义在“方法区”这块内存中,当new String的时候,jvm会检查String的父类,找出父类的定义,并找出哪些是String可以拥有的,然后按照筛选出来的父类定义和String本身的类定义,在堆中分配一个内存块(就是俗称的生成了一个对象),而没有专门为String的父类Object分配空间。

其实你可以想想下,如果像你那么说,new一个String的时候,还会生成一个Object对象,那java简直没法使用了,因为Obejct是所有对象的父类,使用任何对象都会生成一个Object对象,还不算其他的父类。如果这样,多大的内存也不够用,即使内存够了,CPU也会忙死,因为这么多对象,后台的gc执行一次收集清除,都累得够呛了。特别是现在的gc机制都是按代收集的,对象巨多,年轻代很容易满,导致gc收集启动,应用程序会停顿。这样的系统谁能用呢?



我的意思是,在内存中String类的对象,其实只是一个穿了件马甲的Object对象,这样理解可以吗?
0 请登录后投票
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics