阅读 70

JVM类加载机制

类加载过程

  1. 加载: 从磁盘或者网络读取字节码到内存中,如jar、zip

  2. 验证: 验证字节码是否符合class文件规范,比如魔术验证、版本号验证

  3. 准备: 读取字节码中常量池信息、读取static变量为其分配内存,初始化变量值(如int初始化为0)

  4. 解析: 将符号引用解析为直接引用,解析时也可能出现引用类未加载触发类加载,所以类加载是一个递归过程

  5. 初始化: 调用类的client方法,在编译时,会将类中static代码段以及static变量的赋值生成为一个client方法。在初始化阶段执行该方法。

  6. 使用: 比如new创建对象

  7. 卸载: 类的卸载可以回收类所占用的空间(堆区),但只有当加载该类的类加载器被卸载后类才能够卸载。所以这部分内存回收效果并不好,有些jvm虚拟机甚至不提供类卸载功能。

三层加载

类的加载分为了三层结构,首先为了跑起来我们的Java应用,需要加载lib目录下的rt.jar、tools.jar基本类库文件,这部分类由JVM中的BootstrapClassLoader进行加载。除此之外,如果还需要加载扩展类库,只需要放到lib/ext目录下,在jvm运行起来后,由ExtensionClassLoader进行加载。接下来就是加载我们程序员编写的应用程序以及应用程序所依赖的第三方jar包,这部分由JVM提供的ApplicationClassLaoder进行加载。

对于ApplicationClassLoader有个需要注意的点:它会先加载我们编写的代码,再加载第三方jar中的class。

基于这个特性,我们可以直接在代码中修改第三方jar中的类文件。记得有次公司dubbo是公司自己把源码拉下来改造(不太清楚为什么),然后返回值不支持attachment回传功能,其实provider是已经将attachment给回传过来了,但是consume没有处理,当时是通过直接在代码中复制了处理socket的类进行的改造。

双亲委派

因为Java设计是除了Object类之外所有类都具备rt.jar中的Object父类。为了保证这个父类一定是加载rt.jar中的Object,而不是用户自己定义的Object。JVM定义了加载类的顺序: 双亲委派。即查找类自下而上,加载类自上而下。这样的加载顺序使得基础类一定是Bootstrap进行统一加载,而非其它类加载加载的。

如果AppClassLoader可以直接加载类,如果此时用户编写了类java.lang.Object,那么他的代码中的rt.jar中的类执行(obj instanceof Object )就会直接报错了。

打破双亲委派

第一次打破

在早期的JDK中,ClassLoader这个类允许自定义的类加载重写loadClass方法,loaderClass方法本就是双亲委派的逻辑,当时并没有提出双亲委派的概念,子类重新后就会打破这个模型。后来为了规范提出了双亲委派,但是有些代码已经重写了,为了兼容后来jdk官方建议重写findClass方法。

这次打破双亲委派不是为了实现特殊功能,只是在双亲委派提出前乱用导致。

第二次打破

第二次打破是由于在rt.jar中的JNDI服务需要通过SPI机制去加载实现类,比如MySQL的实现。但是问题在于JNDI的接口是在rt.jar中,而SPI实现通常是通过第三方jar包引入存在于classpath之中,这就导致JDNI接口在加载实现类时,无法加载到classpath中的JNDI的实现,而等到AppClassLoader加载实现已经为时已晚了。所以必须要有一个机制可以在BootstrapClassLoader中去加载classpath中的类,jvm提供的解决方式便是Thread.getContextClassLoader()。当Thread创建时,可以通过Thread.getContextClassLoader()设置上下文类加载器,如果没有设置,默认是AppClassLoader。

第二次打破是因为SPI的引入机制。


作者:三丶斤
链接:https://juejin.cn/post/7027114896844128293


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐