什么是类加载机制
- 把描述类的Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机使用的类型。
Class文件表示一串二进制的字节流,无论以何种形式存在都可以。
类初始化时机
- 主动引用
- 当遇到new等字节码指令时,主要场景包括:使用new关键字实例化对象、读取或者设置静态变量、调用类的静态方法;
- 对类进行反射调用;
- 初始化一个类,如果其父类还没有初始化,则先触发其父类的初始化;
- 当虚拟机启动,用户需要制定一个要执行的主类,虚拟机会先初始化这个主类;
- 使用JDK1.7的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始 化,则需要先触发其初始化。
- 被动引用
类加载生命周期
-
加载
主要完成三件事:
- 通过类的完全限定名获取定义该类的二进制字节流;
- 将这个字节流代表的静态存储结构转换为方法区的运行时数据结构;
- 在内存中生成一个Class对象,作为方法区这个类的各种数据的访问入口。
其中二进制字节流可以从以下方式获取:从ZIP包读取;从网络中获取;运行时计算生成,例如动态代理技术。
-
验证
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 -
准备
为类变量分配内存并设置初始值,使用的是方法区的内存。 -
解析
将常量池的符号引用替换为直接引用的过程。 -
初始化
虚拟机执行类构造器x<clinit>
方法的过程。
类与类加载器
类加载器的作用:
- 实现类的加载
- 比较两个类是否相等,需要类本身相等,并且使用同一个类加载器进行加载。
-
类加载器分类
- 启动类加载器:负责加载<JRE_HOME>\lib\rt.jar包下的类库,使用C++实现,是虚拟机自身的部分。
- 其他类加载器:独立于虚拟机,由Java实现
- 扩展类加载器:加载<JAVA_HOME>\lib\ext中的类库到内存,开发者可以直接使用。
- 应用程序加载器:加载classpath上指定的类库,如果没有自己定义过类加载器,一般默认这个就是程序默认的类加载器。
-
双亲委派机制
当类加载器收到加载请求时,首先不会自己去尝试加载,而是把请求委派给父类加载器,因此所有的请求都会传送到顶层的启动类加载器,只有当父类加载器无法完成加载请求时,子加载器才会尝试自己加载。使用双亲委派机制的好处:1. 使得Java类随着他的类加载器一起有了优先级的层次关系。例如位于rt.jar中的Object类,无论哪个类加载器要加载这个类,最后都会由启动类加载器加载,从而保证了Object类在环境中的唯一性。2. 保证Java程序的稳定运行,实现双亲委派的代码都几种在java.lang.ClassLoader的loadClass( )方法中,先检查类是否加载,如果没有加载,就调用父类加载器的loadClass方法,如果父类加载器为空,就调用启动类加载器;如果父类加载器加载失败,抛出异常之后,再调用自己的findClass方法进行加载。
如何自定义的类加载器:将自己的类加载器逻辑写到loadClass方法里面的findClass方法中。
-
破坏双亲委派机制
-
JDBC
JDBC是Java的标准服务,他的代码由启动类加载器去加载,但是它需要调用由独立厂商实现并部署在classpath中的家口提供者代码,但是启动类加载器不认识这些代码,就使用线程上下文加载器去加载,也就是父类加载器委派子类加载器。 -
Tomcat:保证隔离性;支持jsp的修改
Tomcat的隔离性:
- 每个应用程序的类库都是独立的;
- Web容器的类库和程序的类库隔离。
如果Tomcat使用默认的类加载机制,无法保证隔离性,无法加载两个相同类库的不同版本;jsp文件修改之后因为已经加载了这个类,所以不能重新加载。
-
参考资料:周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.