概述首先抛给大家一个问题。这个问题也是我厂同学在做性能分析产品时遇到的问题。同一个类加载器对象是否可以多次加载同一个class文件,得到多个java层可以使用的Class对象?请注意以上描述中的关键词:同一个类加载器:表示不是每次都新建一个类加载器对象。我知道有些对类加载器稍有了解的同学肯定会想到这个。我们这里强调的是加载同一个类加载器对象。同一个类文件:表示类文件中的信息是一致的,没有任何修改,至少名字不能改。因为有些同学会钻空子,比如拿到班级文件改名字,哈哈。多个Class对象:表示每次创建都是一个新的Class对象,返回的不是同一个Class对象。两者都可以被java层使用:也就是说java层可以感知。可能长期关注我的同学公众号看过我的一些文章,知道我在这里说的是什么。不知道的可以看看我之前的文章文章,这里有一个trick,我就不直接告诉你是哪篇文章了,只是稍微提醒一下和内存GC相关的。虽然标题有点党的意思,但我觉得标题里的99.99%应该不夸张,百分比应该更大,但是请认真回答,不要乱选,我知道有人会乱选,哈哈。普通类加载这里说的是普通类加载,这也是我们都理解的一种类加载机制,但是我会讲得更深入一些,从JVM实现的角度。在JVM中,有一个数据结构叫做SystemDictonary。这个结构体主要用来检索我们常说的类信息。这些类信息对应的结构体就是klass。SystemDictonary的理解可以认为是一个Hashtable,关键是类加载器对象。+类名,value是指向klass的地址。这样,当我们的任何一个类加载器正常加载类时,我们都会在这个SystemDictonary中查找是否有这样一个可以返回的klass,如果有就返回,否则就新建一个放上去在结构中,我不会讲委托类加载过程。所以看起来同一个类加载器不可能多次加载同一个类。在正常情况下确实如此。奇怪的现象但是,我们从java进程的内存结构中看到了一些这样的现象。下面是我们性能分析产品的一些截图:这种现象中,名为java.lang.invoke.LambdaForm$BMH的类有多个,它们的类加载器都是BootstrapClassLoader,即同一个类加载器加载同一个类多次。这是我们分析工具的问题吗?显然不是,因为那是我们从记忆中读到的。现象模拟上面的现象看似和lambda有一定关系,但其实不仅仅是lambda的情况,我们可以模拟publicstaticvoidmain(Stringargs[])throwsThrowable{Fieldf=Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafeunsafe=(Unsafe)f.get(null);StringfilePath="/Users/nijiaben/AA.class";byte[]buffer=getFileContent(filePath);Class>c1=不安全。defineAnonymousClass(UnsafeTest.class,buffer,null);Class>c2=unsafe.defineAnonymousClass(UnsafeTest.class,buffer,null);System.out.println(c1==c2);}上面的代码其实就是用的Unsafe对象的defineAnonymousClass方法加载同一个class文件两次得到两个Class对象,最后输出false。这意味着c1和c2实际上是两个不同的对象。因为我们的类文件都是一样的,也就是字节码里面的类名是完全一样的,所以jvm里面的类对象的名字其实也是一样的。但是这里要提一下的是,如果你把c1和c2的名字打印出来,你会发现一些区别。/hashCode值将分别添加在类名之后。这个hash值就是对应Class对象的hashCode值。这其实是JVM中的一种特殊处理。另外,你不能通过其他java层面的API获取这个类,比如Class.forName,所以必须先保存获取到的Class对象,以后再继续使用。defineAnonymousClass的解释defineAnonymousClass的方法比较特殊。从名字上也可以看出创建了一个匿名类,但是匿名的概念和我们理解的匿名是不一样的。这类类的创建通常都有一个宿主类,也就是第一个参数指定的类。这样创建的类就会使用宿主类定义的类加载器来加载这个类。最关键的一点是这个类创建后,不会丢到上面提到的SystemDictonary中。也就是说,我们无法通过普通的类查找,比如Class.forName等API,查到这个类是否已经被定义过。所以过度使用这个API来创建这个类会在一定程度上带来一定的内存泄漏。那么有人会问,如果看不到任何好处,为什么要提供这种api,这样做有什么意义,可以了解一下JSR292。Jvm可以通过InvokeDynamic支持动态类型语言。通过这种方式,我们实际上可以提供一个类模板。在运行时加载类时,会动态替换常量池中的一些内容。这样,同一个class文件,通过加载多次,传入不同的cpPatches,也就是defineAnonymousClass的第三个参数,就可以在运行时产生不同的效果。主要原因是原始的JVM类加载机制不允许这种情况发生,因为同一个类加载器我们只能加载一次同名的类,所以为了支持动态语言的特性,类似的API提供来实现这种效果。总结一般来说,一般情况下,同一个类文件只能被同一个类加载器对象加载一次,但是我们可以使用Unsafe的defineAnonymousClass来实现同一个类文件被同一个类加载器对象加载多次的效果,因为没有放入SystemDictonary,所以我们可以无限加载同一个类。这个一般人不太了解,所以大家在面试的时候,如果能把我文章里的情况说清楚,相信是加分项,但也有可能不小心受伤,因为你的面试官也可能不是清楚是什么情况。【本文为专栏作家李嘉鹏原创文章,转载请微信公众号(你个假笨蛋,id:lovestblog)联系作者授权转载】点此查看本作者更多好文
