概述

首先还是把问题抛给大家,这个问题也是我厂同学在做一个性能分析产品的时候碰到的一个问题。

同一个类加载器对象是否可以加载同一个类文件多次并且得到多个Class对象而都可以被java层使用吗?

请仔细注意上面的描述里几个关键的词:

同一个类加载器:意味着不是每次都new一个类加载器对象,我知道有些对类加载器有点理解的同学肯定会想到这点。我们这里强调的是同一个类加载器对象去加载。

同一个类文件:意味着类文件里的信息都一致,不存在修改的情况,至少名字不能改。因为有些同学会钻空子,比如说拿到类文件然后修改名字啥的,哈哈。

多个Class对象:意味着每次创建都是新的Class对象,并不是返回同一个Class对象。

都可以被java层使用:意味着Java层能感知到,或许对我公众号关注挺久的同学看过我的一些文章,知道我这里说的是什么,不知道的可以翻翻我前面的文章,这里卖个关子,不直接告诉你哪篇文章,稍微提示一下和内存GC有关。

虽然有些标题党的意思,不过我觉得标题里的99.99%说得应该不夸张,这个比例或许应该更大,不过还是请认真作答,不要随便选,我知道肯定有人会随便选的,哈哈。

正常的类加载

这里提正常的类加载,也是我们大家理解的类加载机制,不过我稍微说得深一点,从JVM实现角度来说一下。在JVM里有一个数据结构叫做SystemDictonary,这个结构主要就是用来检索我们常说的类信息,这些类信息对应的结构是klass,对SystemDictonary的理解,可以认为就是一个Hashtable,key是类加载器对象+类的名字,value是指向klass的地址。这样当我们任意一个类加载器去正常加载类的时候,就会到这个SystemDictonary中去查找,看是否有这么一个klass可以返回,如果有就返回它,否则就会去创建一个新的并放到结构里,其中委托类加载过程我就不说了。

那这么一说看起来不可能出现同一个类加载器加载同一个类多次的情况。

正常情况下也确实是这样的。

奇怪的现象

然而我们从java进程的内存结构里却看到过类似这样的一些现象,以下是我们性能分析产品里的部分截图:

在这个现象里,名字为java.lang.invoke.LambdaForm$BMH的类有多个,并且其类加载器都是BootstrapClassLoader,也就是同一个类加载器居然加载了同一个类多次。这是我们的分析工具有问题吗?显然不是,因为我们从内存里读到的就是这样的信息。

现象模拟

上面的这个现象看起来和lambda有一定关系,不过实际上并不仅仅lambda才有这种情况,我们可以来模拟一下

publicstaticvoid main(String args[]) throws Throwable {

Field f = Unsafe.class.getDeclaredField("theUnsafe");

f.setAccessible(true);

Unsafe unsafe = (Unsafe) f.get(null);

String filePath = "/Users/nijiaben/AA.class";

byte[] buffer =getFileContent(filePath);

Class> c1 = unsafe.defineAnonymousClass(UnsafeTest.class, buffer, null);

Class> c2 = unsafe.defineAnonymousClass(UnsafeTest.class, buffer, null);

System.out.println(c1 == c2);

}

上述代码其实就是通过Unsafe这个对象的defineAnonymousClass方法来加载同一个类文件两遍得到两个Class对象,最终我们输出为false。这也就是说c1和c2其实是两个不同的对象。

因为我们的类文件都是一样的,也就是字节码里的类名也是完全一样的,因此在jvm里的类对象的名字其实也都是一样的。不过这里我要提一点的是,如果将c1和c2的名字打印出来,会发现有些区别,分别会在类名后面加上一个/hashCode值,这个hash值是对应的Class对象的hashCode值。这个其实是JVM里的一个特殊处理。

另外你无法通过java层面的其他api,比如Class.forName来获取到这种class,所以你要保存好这个得到的Class对象才能后面继续使用它。

defineAnonymousClass的解说

defineAnonymousClass这个方法比较特别,从名字上也看得出,是创建了一个匿名的类,不过这种匿名的概念和我们理解的匿名是不太一样的。这种类的创建通常会有一个宿主类,也就是***个参数指定的类,这样一来,这个创建的类会使用这个宿主类的定义类加载器来加载这个类,最关键的一点是这个类被创建之后并不会丢到上述的SystemDictonary里,也就是说我们通过正常的类查找,比如Class.forName等api是无法去查到这个类是否被定义过的。因此过度使用这种api来创建这种类在一定程度上会带来一定的内存泄露。

那有人就要问了,看不到啥好处,为啥要提供这种api,这么做有什么意义,大家可以去了解下JSR292。jvm通过InvokeDynamic可以支持动态类型语言,这样一来其实我们可以提供一个类模板,在运行的时候加载一个类的时候先动态替换掉常量池中的某些内容,这样一来,同一个类文件,我们通过加载多次,并且传入不同的一些cpPatches,也就是defineAnonymousClass的第三个参数, 这样就能做到运行时产生不同的效果。

主要是因为原来的JVM类加载机制是不允许这种情况发生的,因为我们对同一个名字的类只能被同一个类加载器加载一次,因而为了能支持动态语言的特性,提供类似的api来达到这种效果。

总结

总的来说,正常情况下,同一个类文件被同一个类加载器对象只能加载一次,不过我们可以通过Unsafe的defineAnonymousClass来实现同一个类文件被同一个类加载器对象加载多遍的效果,因为并没有将其放到SystemDictonary里,因此我们可以无穷次加载同一个类。这个对于绝大部分人来说是不太了解的,因此大家在面试的时候,你能讲清楚我这文章里的情况,相信是一个加分项,不过也可能被误伤,因为你的面试官也可能不清楚这种情况。

【本文是51CTO专栏作者李嘉鹏的原创文章,转载请通过微信公众号(你假笨,id:lovestblog)联系作者本人获取授权】

【编辑推荐】

【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0

java一个类多次加载_据说99.99%的人都会答错的类加载问题相关推荐

  1. java类二次加载_深入理解java之类加载器

    一.类与类加载器 类加载器:实现加载阶段的第一步,通过一个类的全限定名来将这个类的二进制字节流加载进jvm. 类与类加载器:任意一个类唯一性都是由它本身和加载它的类加载器确定,两个类是否相等在它们是由 ...

  2. java中项目启动时加载_如何在项目启动时,加载或解析某配置文件

    在web项目中有很多时候需要在项目启动时就执行一些方法,而且只需要执行一次,比如:加载解析自定义的配置文件.初始化数据库信息等等,在项目启动时就直接执行一些方法,可以减少很多繁琐的操作. 在工作中遇到 ...

  3. java一个类只能创建一个对象吗_一个类只能有一个对象,对么

    一个类只能有一个对象,不对.类是用户定义的一种数据类型,可以使用这个类型来说明一个或多个变量,及对象.类是一种抽象的数据类型,是对对象的抽象:对象是对客观事物的抽象. 一个类只能有一个对象,不对. 类 ...

  4. CSS3使用animation实现一个类音量跳动加载图标

    效果图: 源码: <!DOCTYPE html> <html lang="en"><head><meta charset="UT ...

  5. 分析ThinkPHP5的源码(1) : 类的自动加载

    前文 Composer 下载ThinkPHP5.1的源码,每个框架它都必须都有一个"类的自动加载"机制 ,我们都知道PHP引入文件是需要require . include 才能使用 ...

  6. java 内部类 加载_举例讲解Java的内部类与类的加载器

    内部类 class A { //Inner1 要在 A 初始化后 才能使用,即要被A的对象所调用 class Inner1 { int k = 0; // static int j = 0; //A加 ...

  7. java类是如何加载的?不知道classLoader和双亲委派,不是一个合格的程序员

    目录 详细图送上 类加载器子系统 类的加载过程 加载(loading)阶段 链接(linking) 验证(Verify) 准备(Prepare) 解析(Resolve) 初始化(Initializat ...

  8. 中yeti不能加载_第二十章_类的加载过程详解

    类的加载过程详解 概述 在 Java 中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载 按照 Java 虚拟机规范,从 Class 文件到加载到内 ...

  9. java 类编译_Java类编译、加载、和执行机制

    Java类编译.加载.和执行机制 标签: java 类加载 类编译 类执行 机制 0.前言 个人认为,对于JVM的理解,主要是两大方面内容: Java类的编译.加载和执行. JVM的内存管理和垃圾回收 ...

最新文章

  1. 如何在DNN模块中插入一个图片--在模块中引用资源文件
  2. java gb13000_浅谈 GB13000
  3. 网易市值超百度 成为国内第五大互联网公司
  4. HTML中的表格和表单控件详解
  5. pin与抓握手包破解wifi密码
  6. CAM是利用计算机,利用DroidCam将手机摄像头打造成计算机摄像头
  7. liteIDE配置gocode
  8. python ctype_Python ctype帮助:使用C unsigned char指针
  9. 2020-10-27 史上最全最新机器人领域期刊总结
  10. python 处理阻尼正弦
  11. 这个冬季,你抑郁吗?
  12. 一起聊聊什么是P问题、NP问题、NPC问题
  13. SAP中采购非评估收货应用分析实例
  14. 无监督异常检测中的阈值确定
  15. url存在链接注入漏洞_url跳转漏洞原理及绕过方式
  16. linux系统开机一直卡在logo界面,ubuntu开机卡在ok界面,ubuntu一直在加载界面
  17. 国内十大资质正规黄金交易平台排名(2023名单汇总)
  18. 洋码头 根据关键词取商品列表 API
  19. html css 最佳实践,30个CSS最佳实践 | Soo Smart!
  20. 设计模式之禅(第2版)

热门文章

  1. 不知道还有哪个公司周末培训的啊?
  2. RestTemplate上传图片
  3. Mybatis数据过滤问题
  4. 这才是做生意的正确方法,360行,行行出状元,摆摊也能赚大钱
  5. 深圳计算机维修工考试,深圳职业技能鉴定计算机维修工.doc
  6. matlab杀毒扫描不动,我的电脑每次用360查杀木马扫描的时候,到其中一个文件的时候都会停住,然后进度条就一直不动,这是中毒了吗...
  7. AI人工智能将引入证券监管,数据库蓝海时代来临
  8. numpy 三维数组拼接
  9. 需求管理之需求优先级的排序-需求优先级分析方法论-波士顿矩阵和KANO模型
  10. 微信中直接下载app的解决方法,解决微信中无法直接下载app的问题