jvm类的加载机制
1.加载
通过全限定类名来获取class的字节流,存储到元空间中,并且定义class对象,作为该类的访问入口
2.连接
a.验证(确保被加载的类符合jvm的规范) b. 准备(类变量分配初始值int i =0,int j=0 obj=null) c.解析 (常量池中的符号引用替换为直接引用,在sysout,s变为“abc”)
public class Test{
public static void main() {
String s=”adc”;
System.out.println(“s=”+s);
}
}
3.初始化
a.clinit 类初始化
类变量(静态域)赋值初始化过程,其实是执行类变量代码赋值过程以及执行静态代码块中的逻辑,从上到下执行,int i = 20; int j = 20,obj = Object ,同时虚拟机会保证多线程初始化类的时候被正确的加锁和同步,,注意使用单例的懒汉模式过程中,要加volatile,防止指令重排序,出现返回null的空情况
b.init对象初始化
执行类构造函数
4.使用
5.卸载
类的加载时机
1.主动引用
new,main方法,反射,初始化类的时候,父类没有初始化,调用类的静态成员(final常量除外) 等情况,类需要被先初始化
2.被动引用
通过子类引用父类的静态变量,不会导致子类初始化,而是会导致父类初始化
引用常量池中的常量
通过数组定义类的引用,不会导致类被初始化
双亲委派机制
- 一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试调用自己的findClass()方法进行加载
原因:防止内存中出现多份同样的字节码,越基础的类越有上层类加载器加载,java.lang.object - 类的唯一性:是由全限定类名和类的加载器共同确定
打破双亲委派机制
- TCCL 线程上下文类加载器(默认继承application classloader)
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。 - 问题 基础类无法调用类加载器加载用户提供的代码?
双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),但如果基础类又要调用用户的代码,比如jdbc,但启动类加载器只能加载基础类,无法加载用户类。
解决:
为此 Java 引入了线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过 java.lang.Thread.setContextClassLoaser() 方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
如此,JNDI 服务使用这个线程上下文类加载器去加载所需要的 SPI 代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java 中所有涉及 SPI 的加载动作基本上都采用这种方式,例如 JNDI、JDBC、JCE、JAXB 和 JBI 等。
线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。
参考
https://www.cnblogs.com/czwbig/p/11127222.html
https://blog.csdn.net/yangcheng33/article/details/52631940
网友评论