当前位置: 首页 > 科技观察

JDK15最新版JVM类加载器详解

时间:2023-03-12 10:31:48 科技观察

1类加载器在类加载器家族中具有类似于人类社会的权力等级:1.1Bootstrap由C/C++实现,启动类加载器是最高级别,JVM在启动时创建,通常由os相关的native代码实现,是最基本的类加载器。需要注意的是,在使用JDK8时,BootstrapClassLoader会智能加载一个具有特定名称的类库,例如rt.jar。这意味着我们自定义的jar即使扔到\jre\lib中也不会被加载。负责将/jre/lib或-Xbootclasspath参数指定的路径中被虚拟机识别的类库加载到内存中(按名称识别,如rt.jar,不识别的文件不会加载),如:ObjectSystemStringrt。系统属性sun.boot.class.path指定目录下的jar等特定名称的jar包,JVM启动时通过BootstrapClassLoader加载rt.jar,并初始化sun.misc.Launcher创建ExtensionClassLoader和Application类加载器实例。查看BootstrapClassLoader初始化了哪些类库:URL[]urLs=Launcher.getBootstrapClassPath().getURLs();for(URLurL:urls){System.out.println(urL.toExternalForm());}JDK9负责loading启动时的基础模块类,如:java.basejava.managementjava.xml1.2PlatformClassLoader在JDK8中只有一个ExtensionClassLoader实例,由sun.misc.Launcher$ExtClassLoader实现:负责加载\lib\ext或java.ext。dirs系统变量指定路径下的所有类库加载一些扩展的系统类,如XML、加密、压缩相关的功能类等。当JDK9换成平台类加载器加载一些平台相关的模块时,如作为java.scripting、java.compiler*、java.corba*。那为什么要取消,换成9点呢?JDK8主要是加载jrelib的ext,在扩展jar包的时候使用。这个操作不推荐,所以废除了。而JDK9具有模块化,不需要这样的扩展加载器。1.3ApplicationClassLoader只有一个实例,由sun.misc.Launcher$AppClassLoader实现。JDK8负责加载系统环境变量ClassPath或系统属性java.class.path指定目录下的所有类库。如果应用程序没有定义自己的加载器,加载器也是默认的类加载器。loader可以通过java.lang.ClassLoader.getSystemClassLoader获取。JDK9以后的应用类加载器,用于加载应用级模块,如:jdk.compilerjdk.jartooljdk.jshellclasspath下的所有类库第二层和第三层类加载器是用Java语言实现的,用户可以also1.4自定义类加载器自定义加载器是java.lang.ClassLoader的子类,用户可以自定义类的加载方式;但是自定义类加载器的加载顺序最后是所有系统类加载器的加载顺序。1.5ThreadContextClassLoader每个线程都有一个类加载器(jdk1.2之后引入),称为ThreadContextClassLoader。如果线程创建时没有设置,默认会从父线程继承一个。如果applicationglobalSet中没有一个,那么所有的ThreadContextClassLoader都是ApplicationClassLoader。可以通过Thread.currentThread().setContextClassLoader(ClassLoader)来设置,也可以通过Thread.currentThread().getContextClassLoader()来获取。线程上下文加载器有什么用?类加载器类加载器允许父类加载器通过子类加载器加载需要的类库,这打破了我们下面提到的双亲委派模型。这样做有什么好处?使用线程上下文加载器,我们可以实现所有代码的热替换和热部署,Android中的热更新原理也是基于此。2验证类加载器2.1查看本地类加载器在JDK8环境下,执行结果如下。AppClassLoader的parent是Bootstrap,是C/C++实现的,JVM系统中不存在,所以输出为null。类加载器的特点类加载器不需要等到一个类“第一次被主动使用”才加载它。JVM规范允许类加载器在预期使用类时预加载该类。Java程序不能直接引用启动类加载器,直接将classLoader设置为null,默认使用启动类加载器。如果加载时缺少.class文件,它会在第一次主动使用该类时通知LinkageError。如果没有使用过,则不会报错。如果没有指定父加载器,则默认为启动加载器。每个类加载器都有自己的命名空间,它由加载器及其所有父加载器加载的类组成。不同的命名空间可能具有相同的类全路径名。运行时包由来自同一个类加载器的类组成。判断两个类是否属于同一个运行时包,不仅要看全路径名是否相同,还要看定义的类加载器是否相同。只有属于同一运行时包的类才能相互可见。包中的低级当前类加载器不能覆盖高级类加载器已经加载的类。如果低级类加载器要加载一个未知的类,就必须非常小心。逐层向上礼貌询问:“请问,这个类加载了吗?”被询问的高级类加载器会问自己两个问题:我是否加载了这个类?如果没有,这个类可以加载吗?层次类加载器只有在两个问题的答案都是“否”时,才能让当前类加载器加载未知的类。左边绿色箭头询问这个类是否已经被逐级加载,直到BootstrapClassLoader,然后向下逐级尝试加载这个类,如果没有,通知当前发起加载请求的类加载器,并在右侧的三个小选项卡中允许加载,列出了该级别类加载器主要加载的代表性类库,其实不止于此。通过下面的代码,可以查看Bootstrap中所有加载类库的执行结果。可以添加Bootstrap加载的路径。不建议修改或删除原有加载路径。在JVM中添加如下启动参数,即可正常通过Class.forName读取指定类,说明该参数可以增加Bootstrap的类加载路径:-Xbootclasspath/a:/Users/sss/book/easyCoding/通过Jdk11/src如果想观察启动时加载了哪个jar包中的哪个类,可以加上-XX:+TraceClassLoading这个参数,在解决类冲突的时候很有用。毕竟不同的JVM环境加载类的顺序是不一致的。有时您想观察特定类的加载上下文。由于加载的类很多,调试起来非常困难。很难捕捉到指定类的加载过程。这时候可以使用条件断点功能。比如要查看HashMap的加载过程,在loadClass处设置断点,在条件框中输入,如图。JVM如何在JVM中建立每个类的唯一类的全限定名和加载这个类的类加载器的ID。学习了类加载器的实现机制后,就知道双亲委派模型并不是强制模型。用户可以自定义类加载器。定义一个类加载器怎么样?隔离加载类,隔离某些框架中的中间件和应用模块,将类加载到不同的环境中。比如阿里的一个容器框架使用了自定义的类加载器来保证应用程序依赖的jar包不会影响中间件运行时使用的jar包。修改类加载方式。类的加载模型不是强制的,Bootstrap除外,其他的加载不一定要引入,或者根据实际情况在某个时间点按需动态加载。扩展加载源,比如从数据库、网络甚至电视机顶盒加载,防止源码泄露Java代码容易被编译篡改,可以编译加密,那么类加载器也需要定制恢复加密的字节码。实现自定义类加载器的步骤继承ClassLoader重写findClass()方法调用defineClass()方法一个简单的类加载器实现示例代码如下由于中间件一般都有自己的依赖jar包,在同一个项目中引用在使用多个框架时,你常常被迫按类进行仲裁。按照一定的规则,统一指定jar包的版本,有些类的包路径和类名相同,会造成类冲突,导致应用异常。主流的容器类框架都会自定义类加载器,实现不同中间件之间的类隔离,有效避免类冲突。