JVM学习笔记(6)-类加载机制
1.类的生命周期
虚拟机吧描述类的数据从Class文件加载到内存,并对数据进行校验、转换、解析、初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
与其他在编译时进行连接工作的语言不同,java语言中,类型的加载、连接、初始化都是在程序运行期间完成的,故java可以动态加载和动态连接的特点
类从被加载到虚拟机内存中开始,到卸载为止,整个生命周期包括:加载、连接、初始化、使用、卸载等过程;连接过程有三个部分:验证、准备、解析
加载、验证、准备、初始化、卸载这五个部分的顺序是确定,类加载过程必须按照这个顺序按部就班的执行,而解析阶段则不一定;在某些情况下解析可以在初始化阶段之后在开始,这是为了支持java语言的运行时绑定(动态绑定)
(1)加载
装载类的第一阶段,取得类的二进制流(class文件),转化为方法区的数据结构,在java堆中生成对应的java.lang.Class对象
(2)连接(分三个阶段:验证、准备、解析)
【1】验证:
保证class类格式的正确,如魔数验证、版本号验证等;
源数据验证,如是否有父类、是否继承final类、非抽象类是否实现了所有抽象方法;
字节码验证:运行检查,如栈数据和操作数参数是否吻合、跳转指令是否指到合理位置
符号引用验证:常量池中描述的类是否存在,访问字段或方法是否有权限
【2】准备:
分配内存,并未类设置初始值(方法区中)
如:public static int v=1;在准备阶段被设置为0,在初始化<clinit>时被设置为1
如果改为public static final int v=1;则在准备阶段就直接赋值1;
【3】解析:
符号引用替换成直接引用
符号引用是指字符串引用对象不一定被加载;直接引用是指指针或地址偏移量,引用对象一定在内存中
(3)初始化
执行类构造器<clinit>
static变量赋值
static块执行
子类<clinit>执行前保证父类的<clinit>被调用
<clinit>是线程安全的
2.类在什么时候初始化
在初始化之前必须先进行加载、验证、准备阶段
在下列5中情况中必须立即进行初始化
(1)遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类没有进行过初始化,则需先触发其初始化
使用new 关键字实例化对象的时候
读取或设置一个类的静态字段(被final修饰,在编译器把结果放入常量池的静态字段除外)
调用一个类的静态方法的时候
(2)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需触发其初始化
(3)当初始化一个类的时候,如果父类没有进行过初始化,则需触发其父类初始化
(4)虚拟机启动时,用户需要指定一个要执行的主类,含有main方法的那个类,虚拟机会先初始化这个主类
(5)当使用jdk7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法的句柄所对应的类没有进行初始化,则需先触发其进行初始化
【1】通过子类引用父类的静态字段,不会导致子类初始化。对于静态字段,只有直接定义这个字段的类才会被初始化
【2】通过数组定义定义表引用类,不会触发此类的初始化
【3】被static final 修饰的常量在编译期间就放入到了常量池中,本质上是没有直接引用到定义常亮的类,不会触发其初始化
在testNotInitialization.java的编译期就将ConstClass类的HELLO变量值hello放到了testNotInitialization类的常量池中
【4】当一个类在初始化时需要求其父类已经全部初始化,而接口则不要求这样,只要在真正使用到父接口的时候才会初始化
注意:
类方法中的局部变量是不存在“准备”阶段的;而类变量则由两次赋值的过程,一次是在“准备阶段”赋值为系统初值,一次是在初始化阶段赋值为程序员设置的初始值
3.类加载器ClassLoader
ClassLoader是一个抽象类;
ClassLoader实例将java字节码加载到JVM中
ClassLoader可以定制不同字节码流的获取方式
ClassLoader负载类装载过程中的加载阶段
3.双亲委派模型
启动类类加载器:Bootstrap ClassLoader,C++语言编写,作用是加载rt.jar,可以使用-Xbootclasspath设置启动类加载器加载的路径
扩展类加载器:Extension ClassLoader,负责加载JAVA_HOME\lib\ext目录中的jar
应用程序加载器:Application Classloader,负责加载用户路径下classpath的类
双亲委派模型工作过程:
如果一个类加载器收到类加载的请求,首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成(注意,启动类加载器不是任何类加载器的父类加载器),所有的加载请求都会传送到顶层的启动类加载器中,只要当父类加载器无法加载的时候(他的搜索范围没有找到该类)时,子加载器才会尝试自己去加载。
双亲委派模型的好处:
比如自己写的Object类是永远不会被加载的,该类的加载请求会传到顶层加载器加载的是rt.jar中的Object.class,避免了类被覆盖
双亲委派模型也不是一成不变的:
可以使用反射的方式使用特定的类加载器去加载某个类
也可以使用Thread.setContextClassLoader()方法进行设置
tomcat中WebappClassLoader会加载自身的类,而不会委派给其父类
osgi中ClassLoader形成网状结构,根据需要由特定的Bundle加载器加载,每个bundle都有自己的类加载器