深入Java虚拟机(番外篇)ClassLoader 初探

news/2024/5/19 3:02:08 标签: jvm, java, class, 反射
class="baidu_pl">
class="article_content clearfix">
class="markdown_views prism-github-gist">

本篇文章是在深入Java虚拟机(四)的基础上产生的一些想法,从另一个方面研究下类加载问题。

面向接口编程

在项目工程目录定义了一个获取钱包余额的接口,而作为普通人会返回50大洋,供你吃喝玩乐。

class="prism language-class="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">interface class="token class-name">Pocket class="token punctuation">{
    class="token keyword">int class="token function">getBalanceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
class="token punctuation">}
class="token keyword">public class="token keyword">class class="token class-name">NormalUser class="token keyword">implements class="token class-name">Pocketclass="token punctuation">{
    class="token annotation punctuation">@Override
    class="token keyword">public class="token keyword">int class="token function">getBalanceclass="token punctuation">(class="token punctuation">) class="token punctuation">{
        class="token keyword">return class="token number">50class="token punctuation">;
    class="token punctuation">}
class="token punctuation">}

我在桌面编译了另一个版本的NormalUser,给我的钱包提提额度。

class="prism language-class="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class-name">NormalUser class="token keyword">implements class="token class-name">Pocketclass="token punctuation">{
    class="token annotation punctuation">@Override
    class="token keyword">public class="token keyword">int class="token function">getBalanceclass="token punctuation">(class="token punctuation">) class="token punctuation">{
        class="token keyword">return class="token number">50000class="token punctuation">;
    class="token punctuation">}
class="token punctuation">}

现在我想改善下生活品质,于是我做了下面的事情:

在项目中自定义一个类加载器:

class="prism language-class="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class-name">LocalClassLoader class="token keyword">extends class="token class-name">ClassLoader class="token punctuation">{
    class="token annotation punctuation">@Override
    class="token keyword">protected Classclass="token operator"><class="token operator">?class="token operator">> class="token function">findClassclass="token punctuation">(String nameclass="token punctuation">) class="token keyword">throws ClassNotFoundException class="token punctuation">{
        String path class="token operator">= class="token string">"/Users/lijie/Desktop/NormalUser.class"class="token punctuation">;
        class="token keyword">try class="token punctuation">{
            FileInputStream ins class="token operator">= class="token keyword">new class="token class-name">FileInputStreamclass="token punctuation">(pathclass="token punctuation">)class="token punctuation">;
            class="token keyword">int size class="token operator">= insclass="token punctuation">.class="token function">availableclass="token punctuation">(class="token punctuation">)class="token punctuation">;
            class="token keyword">byteclass="token punctuation">[class="token punctuation">] clazzBytes class="token operator">= class="token keyword">new class="token class-name">byteclass="token punctuation">[sizeclass="token punctuation">]class="token punctuation">;
            class="token keyword">if class="token punctuation">(insclass="token punctuation">.class="token function">readclass="token punctuation">(clazzBytesclass="token punctuation">) class="token operator">> class="token number">0class="token punctuation">) class="token punctuation">{
                class="token keyword">return class="token function">defineClassclass="token punctuation">(class="token string">"hua.lee.classloader.NormalUser"class="token punctuation">, clazzBytesclass="token punctuation">, class="token number">0class="token punctuation">, sizeclass="token punctuation">)class="token punctuation">;
            class="token punctuation">} class="token keyword">else class="token punctuation">{
                class="token keyword">throw class="token keyword">new class="token class-name">ClassNotFoundExceptionclass="token punctuation">(class="token punctuation">)class="token punctuation">;
            class="token punctuation">}
        class="token punctuation">} class="token keyword">catch class="token punctuation">(class="token class-name">IOException eclass="token punctuation">) class="token punctuation">{
            eclass="token punctuation">.class="token function">printStackTraceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
        class="token punctuation">}
        class="token keyword">throw class="token keyword">new class="token class-name">ClassNotFoundExceptionclass="token punctuation">(class="token punctuation">)class="token punctuation">;
    class="token punctuation">}
class="token punctuation">}

LocalClassLoader写的比较死板,但是挣钱的思路是有了的。。。。

LocalClassLoader重写了 findClass方法,没有重写loadClass,这样双亲委托机制还有效。对于系统中查找不到的类型,都会走到这里,而在findClass方法中,我就会把桌面的NormalUser加载进来。

我们看下测试代码:

class="prism language-class="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class-name">ClassLoaderTest class="token punctuation">{
    class="token keyword">public class="token keyword">static class="token keyword">void class="token function">mainclass="token punctuation">(Stringclass="token punctuation">[class="token punctuation">] argsclass="token punctuation">) class="token keyword">throws ClassNotFoundExceptionclass="token punctuation">, IllegalAccessExceptionclass="token punctuation">, InstantiationException class="token punctuation">{
        Classclass="token operator"><class="token operator">?class="token operator">> clazz class="token operator">= Classclass="token punctuation">.class="token function">forNameclass="token punctuation">(class="token string">"hua.lee.classloader.NormalUser"class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"系统自带 ClassLoader=" class="token operator">+ clazzclass="token punctuation">.class="token function">getClassLoaderclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;
        Pocket pocket class="token operator">= class="token punctuation">(Pocketclass="token punctuation">) clazzclass="token punctuation">.class="token function">newInstanceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"系统自带:"class="token operator">+pocketclass="token punctuation">.class="token function">getBalanceclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;

        class="token comment">//没有重写 loadClass,维持双亲委派机制
        LocalClassLoader lcl class="token operator">= class="token keyword">new class="token class-name">LocalClassLoaderclass="token punctuation">(class="token punctuation">)class="token punctuation">;
        class="token comment">//我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
        class="token comment">//其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
        class="token comment">//defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
        Classclass="token operator"><class="token operator">?class="token operator">> clazzOut02 class="token operator">= lclclass="token punctuation">.class="token function">loadClassclass="token punctuation">(class="token string">"hua.lee.classloader.NormalUser_Temp"class="token punctuation">)class="token punctuation">;

        class="token comment">//双亲委托机制先检查当前类加载器中有没有这个类的 Class 实例,没有的话再搜寻父类,
        class="token comment">//查找到 hua.lee.classloader.NormalUser 的 Class 实例,就返回该对象
        Classclass="token operator"><class="token operator">?class="token operator">> clazzOut01 class="token operator">= lclclass="token punctuation">.class="token function">loadClassclass="token punctuation">(class="token string">"hua.lee.classloader.NormalUser"class="token punctuation">)class="token punctuation">;


        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"clazzOut01 自定义 ClassLoader=" class="token operator">+ clazzOut01class="token punctuation">.class="token function">getClassLoaderclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;
        pocket class="token operator">= class="token punctuation">(Pocketclass="token punctuation">) clazzOut01class="token punctuation">.class="token function">newInstanceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"clazzOut01 自定义加载器获取余额:"class="token operator">+pocketclass="token punctuation">.class="token function">getBalanceclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;

        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"clazzOut02 自定义 ClassLoader=" class="token operator">+ clazzOut02class="token punctuation">.class="token function">getClassLoaderclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;
        pocket class="token operator">= class="token punctuation">(Pocketclass="token punctuation">) clazzOut02class="token punctuation">.class="token function">newInstanceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"clazzOut02 自定义加载器获取余额:"class="token operator">+pocketclass="token punctuation">.class="token function">getBalanceclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;

        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(clazzclass="token operator">==clazzOut01class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(clazzclass="token operator">==clazzOut02class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(clazzOut01class="token operator">==clazzOut02class="token punctuation">)class="token punctuation">;
    class="token punctuation">}

控制台输出是这样的:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
clazzOut01 自定义加载器获取余额:50000
clazzOut02 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
clazzOut02 自定义加载器获取余额:50000
false   //clazz==clazzOut01
false   //clazz==clazzOut02
true    //clazzOut01==clazzOut02

感觉找到了致富的方向。虚拟机里的加载约束没看到呢.

面向抽象类编程

上面的致富之路好像很顺利,那我们尝试用如下的方式:

class="prism language-class="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">abstract class="token keyword">class class="token class-name">Pocket class="token punctuation">{
    class="token keyword">abstract class="token keyword">int class="token function">getBalanceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
class="token punctuation">}
class="token keyword">public class="token keyword">class class="token class-name">NormalUser class="token keyword">extends class="token class-name">Pocketclass="token punctuation">{
    class="token annotation punctuation">@Override
    class="token keyword">public class="token keyword">int class="token function">getBalanceclass="token punctuation">(class="token punctuation">) class="token punctuation">{
        class="token keyword">return class="token number">50class="token punctuation">;
    class="token punctuation">}
class="token punctuation">}

同样,我在桌面编译了一个富人版本:

class="prism language-class="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class-name">NormalUser class="token keyword">extends class="token class-name">Pocketclass="token punctuation">{
    class="token annotation punctuation">@Override
    class="token keyword">public class="token keyword">int class="token function">getBalanceclass="token punctuation">(class="token punctuation">) class="token punctuation">{
        class="token keyword">return class="token number">50000class="token punctuation">;
    class="token punctuation">}
class="token punctuation">}

LocalClassLoader和测试代码没变,但是打印结果确实这样的:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
Exception in thread "main" class="tags" href="/tags/JAVA.html" title=java>java.lang.AbstractMethodError
	at hua.lee.classloader.ClassLoaderTest.main(ClassLoaderTest.class="tags" href="/tags/JAVA.html" title=java>java:25)

虚拟机在解析抽象类实现接口实现的时候有什么区别么

不成熟的推测

首先面向接口中的模式应该是躲过了装载约束的,或者虚拟机设计上是支持接口这么搞的。

既然提示AbstractMethodError,那我把测试代码改一下,不再用抽象类Pocket,直接指向NormalUser:

class="prism language-class="tags" href="/tags/JAVA.html" title=java>java">class="token keyword">public class="token keyword">class class="token class-name">ClassLoaderTest class="token punctuation">{
    class="token keyword">public class="token keyword">static class="token keyword">void class="token function">mainclass="token punctuation">(Stringclass="token punctuation">[class="token punctuation">] argsclass="token punctuation">) class="token keyword">throws ClassNotFoundExceptionclass="token punctuation">, IllegalAccessExceptionclass="token punctuation">, InstantiationException class="token punctuation">{

        Classclass="token operator"><class="token operator">?class="token operator">> clazz class="token operator">= Classclass="token punctuation">.class="token function">forNameclass="token punctuation">(class="token string">"hua.lee.classloader.NormalUser"class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"系统自带 ClassLoader=" class="token operator">+ clazzclass="token punctuation">.class="token function">getClassLoaderclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;
                    class="token comment">//请注意此处⬇️
        NormalUser pocket class="token operator">= class="token punctuation">(NormalUserclass="token punctuation">) clazzclass="token punctuation">.class="token function">newInstanceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
                    class="token comment">//请注意此处⬆️
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"系统自带:"class="token operator">+pocketclass="token punctuation">.class="token function">getBalanceclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;

        class="token comment">//没有重写 loadClass,维持双亲委派机制
        LocalClassLoader lcl class="token operator">= class="token keyword">new class="token class-name">LocalClassLoaderclass="token punctuation">(class="token punctuation">)class="token punctuation">;
        class="token comment">//我们加个后缀名,这样双亲委托查找失败了,就调用LocalClassLoader.findClass 方法加载
        class="token comment">//其实 findClass 方法还是加载 NormalUser类,只是换了个位置和方式:
        class="token comment">//defineClass("hua.lee.classloader.NormalUser", clazzBytes, 0, size);
        Classclass="token operator"><class="token operator">?class="token operator">> clazzOut02 class="token operator">= lclclass="token punctuation">.class="token function">loadClassclass="token punctuation">(class="token string">"hua.lee.classloader.NormalUser_Temp"class="token punctuation">)class="token punctuation">;

        class="token comment">//双亲委托机制会自动搜寻父类,
        class="token comment">//查找到 hua.lee.classloader.NormalUser 的 Class 实例,就返回该对象
        Classclass="token operator"><class="token operator">?class="token operator">> clazzOut01 class="token operator">= lclclass="token punctuation">.class="token function">loadClassclass="token punctuation">(class="token string">"hua.lee.classloader.NormalUser"class="token punctuation">)class="token punctuation">;


        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"clazzOut01 自定义 ClassLoader=" class="token operator">+ clazzOut01class="token punctuation">.class="token function">getClassLoaderclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;
        pocket class="token operator">= class="token punctuation">(NormalUserclass="token punctuation">) clazzOut01class="token punctuation">.class="token function">newInstanceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"clazzOut01 自定义加载器获取余额:"class="token operator">+pocketclass="token punctuation">.class="token function">getBalanceclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;

        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"clazzOut02 自定义 ClassLoader=" class="token operator">+ clazzOut02class="token punctuation">.class="token function">getClassLoaderclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;
        pocket class="token operator">= class="token punctuation">(NormalUserclass="token punctuation">) clazzOut02class="token punctuation">.class="token function">newInstanceclass="token punctuation">(class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(class="token string">"clazzOut02 自定义加载器获取余额:"class="token operator">+pocketclass="token punctuation">.class="token function">getBalanceclass="token punctuation">(class="token punctuation">)class="token punctuation">)class="token punctuation">;

        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(clazzclass="token operator">==clazzOut01class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(clazzclass="token operator">==clazzOut02class="token punctuation">)class="token punctuation">;
        Systemclass="token punctuation">.outclass="token punctuation">.class="token function">printlnclass="token punctuation">(clazzOut01class="token operator">==clazzOut02class="token punctuation">)class="token punctuation">;
    class="token punctuation">}
class="token punctuation">}

惊喜的事情来了,输出变成了下面这样:

系统自带 ClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
系统自带:50
clazzOut01 自定义 ClassLoader=hua.lee.classloader.LocalClassLoader@60e53b93
Exception in thread "main" class="tags" href="/tags/JAVA.html" title=java>java.lang.ClassCastException: hua.lee.classloader.NormalUser cannot be cast to hua.lee.classloader.NormalUser
	at hua.lee.classloader.ClassLoaderTest.main(ClassLoaderTest.class="tags" href="/tags/JAVA.html" title=java>java:24)

这个异常说明了一个问题我们自定义加载进来的hua.lee.classloader.NormalUser不是虚拟机想要的hua.lee.classloader.NormalUser

第一个问题:虚拟机怎么知道这两个NormalUser不一样的?

定义类装载器有关系,同一个全限定名的类型,如果不是同一个定义类装载器,虚拟机就认为不是同一个类型。(按照装载约束应该报错的)

啥是定义类装载器?就是真正把 class 文件装载进来的那个ClassLoader

第二个问题:为什么接口可以这样跑呢?

那是因为面向接口实现的版本没有明确指名类型NormalUser,类型转换都是按照Pocket类型执行方法,虚拟机自动查找接口类型的实现。只要是Pocket的实现就行。

如果你把pocket = (Pocket) clazzOut01.newInstance();换成pocket = (NormalUser) clazzOut01.newInstance();,此时,面向接口的版本也会报ClassCastException

对于虚拟机来说,在接口的实现类,它并不 care 你的实现类的来源(哪个加载器加载的无所谓啦),符合接口定义就可以了。

但是当你指定类型转换的时候,它就要去做类型检查了。

第三个问题:抽象类为什么不行?

首先 YY 一下class="tags" href="/tags/JAVA.html" title=java>java.lang.AbstractMethodError

Pocket为抽象类时,根据抽象类的定义,要有特定实现类来执行。
而对于pocket = (Pocket) clazzOut01.newInstance();这句话,Pocket的强制类型转换是成功(因为解析Poket类型需要执行双亲委派逻辑,解析后发现大家都是同一个Poket),但是当寻找实现类时就发现这个clazzOut01.newInstance()对象对应的类型NormalUser不是自己想要的,虚拟机以为没有对应的实现,所以当执行到pocket.getBalance()时,虚拟机以为程序要单纯的执行Pocket的抽象方法,于是报错class="tags" href="/tags/JAVA.html" title=java>java.lang.AbstractMethodError

如果上面的理解了,那么class="tags" href="/tags/JAVA.html" title=java>java.lang.ClassCastException也就好理解了:

对于pocket = (NormalUser) clazzOut01.newInstance();这句话,虚拟机会把clazzOut01.newInstance()产生的对象强制转换为NormalUser,到这里就回到第一个问题的答案了:clazzOut01所代表的NormalUser和要强转的NormalUser不是一个类型,虚拟机就直接报错了

结语

为啥这个要有结语呢?

因为不成熟的推测真的是本人 YY 的结果,不敢保证真实有效。如有错误之处,还望不吝赐教。

下一篇会续上Java虚拟机(五)垃圾收集(老是感觉要写写垃圾分类啥的,猪都不能吃的那种)


http://www.niftyadmin.cn/n/1568350.html

相关文章

深入Java虚拟机(五)垃圾收集

Java 虚拟机的堆里存放着程序运行中所创建的所有对象。虚拟机可以使用new、newarray、anewarray和multianewarray指令来创建对象&#xff0c;但是没有明确的代码来释放它们。垃圾收集就是自动释放不再被程序所使用的对象的过程。 本篇文章并不是要描述正式的 Java 垃圾收集器&…

Android sharedUserId研究

签名简介&#xff1a; 在Android系统中&#xff0c;所有安装到系统的应用程序都必有一个数字证书&#xff0c;此数字证书用于标识应用程序的作者和在应用程序之间建立信任关系,。这个数字证书并不需要权威的数字证书签名机构认证&#xff0c;它只是用来让应用程序包自我认证的…

深入Java虚拟机(六)线程同步

可以在语言级支持多线程是Java语言的一大优势&#xff0c;这种支持主要集中在同步上&#xff0c;或调节多个线程间的活动和共享数据。Java所使用的同步是监视器。 监视器Monitor Java中的监视器支持两种线程&#xff1a;互斥和协作 虚拟机通过对象锁来实现互斥&#xff0c;允…

对Java Serializable(序列化)的理解和总结

1、序列化是干什么的&#xff1f;简单说就是为了保存在内存中的各种对象的状态&#xff08;也就是实例变量&#xff0c;不是方法&#xff09;&#xff0c;并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states&#xff0c;但是Java给你提…

深入Java虚拟机(思维导图)

本篇是对深入Java虚拟机的总结梳理&#xff0c;原始.xmind文件在这里下载

Android中颜色表示及设置

颜色表示 在Android应用开发中颜色通常是用八位的十六进制的数字表示&#xff0c;例如&#xff1a; 0xffff00ff 这个是int型的数据&#xff0c;其中0x不用多说了&#xff0c;十六进制的前缀&#xff0c;前边的两个ff表示颜色的透明度&#xff0c;范围为00&#xff5e;ff&am…

深入Android系统(一)Build系统

深入Android系统这本书是以Android5.0为基础讲解&#xff0c;但本人使用的是Android9.0的源码&#xff0c;所以和原书内容会有些出入。 对于Android的构建系统&#xff0c;在Android7.0之后Google就已经使用Soong构建系统&#xff0c;旨在取代 Make。它利用 Kati GNU Make 克隆…

HashMap原理及详解

HashMap是我们使用非常多的Collection&#xff0c;它是基于哈希表的 Map 接口的实现&#xff0c;以key-value的形式存在。在HashMap中&#xff0c;key-value总是会当做一个整体来处理&#xff0c;系统会根据hash算法来来计算key-value的存储位置&#xff0c;我们总是可以通过ke…