Java对象创建的过程
虚拟机在碰到一条 new(或getstatic,putstatic,invokestatic) 指令时,会先去常量池中查看是否有该类的符号引用,并且检查这个类是否已被加载,解析和初始化,如果没有,那么就会执行这个类的加载过程。
以下说明5中主动初始化的场景,除此之外,所有引用类的方法不会触发初始化,成为被动引用。
-
使用 new 关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰,已在编译器放入常量池的除外)的时候,以及调用一个类的静态方法的时候。
-
使用java.lang.reflect包方法的对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发初始化。
-
当初始化一个类的时候,发现其父类还没有被初始化,则需要先触发其父类的初始化。
-
当虚拟机启动时,包含main()方法的那个类需要得到初始化。
-
当使用JDK1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发初始化。
多说一句,一个接口在初始化时,并不要求其父接口全部完成了初始化,还有在用到了父接口的时候(如引用父接口中定义的变量)才会初始化。
类加载的过程
![](https://img.haomeiwen.com/i3739049/c603fc06a01a16f6.png)
如上图,类加载共需经历5个过程,其中最后两个过程(解析和初始化的顺序不一定),Java语言支持动态绑定,动态绑定时会先执行初始化在执行解析阶段。
- 加载
加载阶段,虚拟机需要完成3件事情
- 通过一个类的全限定名来获取定义这个类的二进制字节流。
- 将这个静态字节流转化为方法区的运行时的数据结构。
- 在内存中生成一个Class对象,作为方法区访问这个类的各种数据的入口。
- 验证
这一步的目的是为了确保Class文件中所包含信息是否符合JVM规范,并且不会危害JVM自身的安全。
- 文件格式验证
验证文件是否符合Class文件规范
- 元数据验证
是否有父类?是否继承了不该继承的类?是否实现了接口当中的全部方法等
- 字节码验证
保证字节码指令不会跳转到方法体以外的字节码上。
保证方法体中的类型转化是有效的。比如子类类型的对象可以赋值给父类对象,反之则不行。
- 符号引用验证
该阶段的校验发生在虚拟机将符号引用转换为直接引用的时候,这个转化动作将在解析的时候发生。
符号引用验证可以看做是对类自身以为(常量池中的各种符号引用)的信息进行匹配性校验,通常需要校验以下内容。
符号引用中的能否通过类的全限定名找到该类?
在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
符号引用中的类,字段,方法的访问性是否可以被当前类所访问。
- 准备
准备阶段是正式为类变量分配内存并设置变量初始值的阶段,这里所说的设置初始值的意思是给这些变量赋零值(false,0,null等),而不是程序中的初始化赋值。
- 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
-
符号引用: 可以是任意字面量,只要能无歧义的定位至目标即可。符号引用的目标不一定已经加载至虚拟机中。
-
直接引用: 直接指向目标的指针,相对偏移量,或者为一个间接定位至目标的句柄。直接引用所指向的目标一定在内存中存在。
- 初始化
类初始化阶段是类加载阶段的最后一步,除了加载阶段可能会用到用户自定义的类加载器外,其余步骤完全由虚拟机主导,直到该阶段--初始化阶段,才真正开始执行类中定义的Java程序代码。
类和类加载器
对任意一个类,都需要该类和加载该类的类加载器来确定其早Java虚拟机中的唯一性。
对上面这句话的解读就是一个类如果被不同的类加载器加载,那么这两个类必定不相等。
双亲委派模型
从Java虚拟机的角度看,只存在两种不同的类加载器:
-
启动类加载器:BootStrap ClassLoader,这个类加载器由C++语言实现,是虚拟机自身的一部分。
-
所有其他的类加载器: 这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader
双亲委派模型要求除了顶层启动类累加器外,其余的类加载器都必须有自己的父类加载器。
双亲委派模型工作的过程是,如果一个类加载器收到了一个加载类的请求,它不会立刻进行类的加载处理,而是先查看是否有父类加载器存在,如果有,则将这个类加载请求交给父类加载器去加载,只有当父类加载器无法完成这个加载请求时,子类加载器才会尝试自己去加载。