fegin需要实现类_深入理解Java类加载器机制

前言

Java里面的类加载机制,可以说是Java虚拟机核心组件之一,掌握和理解JVM虚拟机的架构,将有助于我们站在底层原理的角度上来理解Java语言,这也是为什么我们学习一个新的知识时,如果不理解原理全靠死记硬背,我相信过不了几天便会忘记的一干二净。

Java是一门跨平台的语言,而JVM虚拟机则在这中间扮演了非常重要的角色,对于我们编写的.java文件,在编译期间会被转换成二进制的class文件,我们也叫做bytecode(字节码),那么这些class文件是如何被加载进JVM虚拟机里面,又是如何被执行呢?

6e519ecab01fa361a668c05faddb167b.png

这就引入了今天我们文章要重点分析的知识之Java类加载器,在此之前我们重新来回顾下JVM的执行架构,借用网上的一张图片,可以非常直观的帮助我们了解:

9813afea7624bc2601eba0d199fd8c42.png

Java虚拟机的核心由三个重要的组件构成:

(1)类加载系统

(2)运行时数据区域

(3)执行引擎

在这里面我们需要重点理解和掌握的包括,类加载机制,运行时数据区域,及执行引擎里面的GC回收器的算法和原理。

运行时数据区域在前面文章已经介绍过,gc算法和原理打算放下一篇文章单聊,本篇文章我们重点介绍类加载器机制。

文章开头我们提到过我们写的java源码文件,在编译后会转成二进制的字节码的class文件,如果我们想要使用它们,那么必须通过类加载器加载处理之后才能使用。

为什么需要类加载器

从广义的概念上Java语言里面只有两种类加载器:

(1)Bootstrap CLassloder(引导类加载器)

(2)User Define Classloader(用户自定义的类加载器)

引导类加载器是本身就是JVM规范的一部分,它与OS平台有关,依赖于OS的实现方式加载类型(包括Java API的类和接口),所以在Java里面引导类加载器只能是native实现的,尽管它是所有类加载器的父加载器,但它却不是Java实现的,所以Java里面引导加载器返回的是null。

Java的引导加载器是严格封闭的,因为其作用就是负责加载Java核心的基础库如rt.jar等,这里面就包含了我们常用的java.lang.xxx等相关类,引导类加载的库保证了类型安全,如果你想自定义一个Long类来替换Java基础库的Long类几乎是做不到的。

而自定义的类加载器机制则提供了非常灵活的扩展机制,允许我们自定义加载器来实现一些特殊的功能。

为什么需要自定义类加载器?

这里列举几种场景:

(1)加密。对字节码加密,Java的类文件可以被很容易反编译,为了提高安全性,我们再编译的时候可以加入加密算法,改变二进制文件的编码,然后在定义专门的来加载器来加载加密后文件,在加载之前解密二进制字节码,在加载,这样就可以提高安全性。

(2)以非标准的方式加载类文件。 比如我们的类文件存放在数据库,FTP,或者在从某个网站上下载。

(3)在运行时候动态的去系统外部加载运行一个类。

(4)在同一个应用中,通过类加载器实现环境或者资源的隔离。

(5)通过类加载器实现灵活的可插拔机制。

Java类加载器的双亲委派机制

从上面可以看到自定义类加载器的强大之处,在我们要实现自定义的类加载器之前,我们需要先了解下Java里面的类加载器是如何加载类的。

Java里面的ClassLoader类是实现自定义类加载器的关键,ClassLoader类是一个抽象类,其提供了自定义类加载器的通用描述,其主要的子类如下:

ClassLoader  SecureClassLoader URLClassLoader ExtClassLoader AppClassLoader

根据Java平台的具体实现,实际的类加载器顺序如下:

513b225b7f2d04ddc298e25e08cb9b6b.png

这里大家需要注意一点,类加载器的顺序并不是所谓的继承关系,其实是逻辑组合关系。

前面提到过引导类加载器是所有加载器的前提,尽管Java语言里面不存在具体的这个类,因为其与操作系统有关,所以是native方法实现。但其却是Java里面所有类加载器名副其实的父加载器,其加载的资源路径是:

%JAVA_HOME%/jre/lib

接着我们看ExtClassLoader加载器的,加载路径是

%JAVA_HOME%/jre/lib/ext或者是java.ext.dirs属性里面配置的路径

最后是AppClassLoader加载器,其加载的资源路径是:

当前的classpath的路径

通过上面的分析,我们能够看到其实类加载器的本质是,加载了什么路径下的资源文件,对于上面的几个类加载的路径,我们可以在Java虚拟机启动类Launcher源码中找到答案:

其中引导类加载器的路径是:

System.getProperty("sun.boot.class.path");

ExtClassLoader类加载器的路径是:

System.getProperty("java.ext.dirs")

最后AppClassLoader类加载器的路径是:

System.getProperty("java.class.path")

通过下面这个测试方法,就可印证:

public static void showClassLoaderForeachPath(){ System.out.println(); //BoostrapClassLoader String[] split=System.getProperty("sun.boot.class.path").split(":"); for(String data:split){ System.out.println(data); } System.out.println("==================="); //ExeClassLoader String[] split1=System.getProperty("java.ext.dirs").split(":"); for(String data:split1){ System.out.println(data); } System.out.println("==================="); //AppClassLoader String[] split2=System.getProperty("java.class.path").split(":"); for(String data:split2){ System.out.println(data); } System.out.println("================"); }

接着我们随便定义一个测试类,看看该类的加载器的情况:

public static void showClassLoaderPath(){ System.out.println(ClassLoaderTest.class.getClassLoader()); System.out.println(ClassLoaderTest.class.getClassLoader().getParent()); System.out.println(ClassLoaderTest.class.getClassLoader().getParent().getParent()); System.out.println("------------------------------------"); System.out.println(int.class.getClassLoader()); System.out.println(Long.class.getClassLoader()); }

输出结果:

sun.misc.Launcher$AppClassLoader@511d50c0sun.misc.Launcher$ExtClassLoader@5e481248null------------------------------------nullnull

可以看到我们自定义的类都是由AppClassLoader这个类加载器加载的,而AppClassLoader是谁由加载呢?

在第二行代码地方能看到是ExtClassLoader加载的,注意这里再次强调类加载器层次非继承关系。

然后我们接着看ExtClassLoader类加载器的父类,发现输出的是null,这在前面已经说了引导加载器是native实现的,所以在Java里面是访问不到的所以是null。

到这里,我们的疑问点集中在为什么类加载器非继承关系,因为在上面的类图里面AppClassLoader与ExtClassLoader是平级兄弟关系,那么为什么说AppClassLoader是由ExtClassLoader作为父类加载器呢?

答案就在源码中,首先看下ClassLoader这个抽象类的构造函数:

//1  protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); }//2 protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); }//3 private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; } }

我们发现无参和一参的构造函数都是调用二参构造函数,二参构造函数的第二个参数恰恰就是指定的父类加载器,如果使用的是无参构造函数,默认调用是:

public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; }

接着看initSystemClassLoader这个方法,这个方法里面有个关键的地方在于调用了sun.misc.Launcher之后,从这个类里面获取了ClassLoader实例:

sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; scl = l.getClassLoader(); }

接着我们看下Launcher类的构造方法时如何定义的:

public Launcher() { Launcher.ExtClassLoader var1; try { //1 var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值