`

笔记]类加载器

阅读更多

http://www.iteye.com/topic/68170

 

看了王森的《JAVA深度历险》,做了一个简单的学习笔记。
1. JAVA类装载器在装载类的时候是按需加载的,只有当一个类要使用(使用new 关键字来实例化一个类)的时候,类加载器才会加载这    个类并初始化。
    类Main:
   

java 代码
 
  1. public class Main {  
  2.     public static void main(String[] args) {  
  3.         A a = new A();  
  4.         a.print();  
  5.         B b = new B();  
  6.         b.print();  
  7.     }  
  8. }  

  
  类A:

java 代码
 
  1. public class A  {  
  2.     public void print() {  
  3.         System.out.println("Using Class A");  
  4.     }  
  5. }  


 类B:

java 代码
 
  1. public class B {  
  2.     public void print() {  
  3.         System.out.println("Using Class B");  
  4.     }  
  5. }  


执行:java -varbose:class Main
执行结果:
    E:\DEV>java -verbose:class Main
    [Opened C:\Program Files\Java\jre1.5.0_11\lib\rt.jar] (类装载器会首先加载rt.jar加载基础类)
    .
    .
    [Loaded Main from file:/E:/DEV/] (类装载器载入相应类并初始化)
    [Loaded A from file:/E:/DEV/]
    Using Class A
    [Loaded B from file:/E:/DEV/]
    Using Class B
2. 让JAVA程序具有动态性
   使用显式方式来实现动态性,我们需要自己动手处理类载入时的细节部分。

     两种方法:
    |
    +-- 隐式的 : 使用new关键字让类加载器按需求载入所需的类
    |
    +-- 显式的 :
                     |
                     +-- 由 java.lang.Class的forName()方法加载
                     |
                     +-- 由 java.lang.ClassLoader的loadClass()方法加载

    (1) 使用Class.forName()
    Class.forName()方法具有两个重载的方法:
            +- public static Class forName(String className)
            |
            +- public static Class forName(String className, boolean initialize,ClassLoader loader)
    参数说明:
        className - 所需类的完全限定名
        initialize - 是否必须初始化类(静态代码块的初始化)
        loader - 用于加载类的类加载器
调用只有一个参数的forName()方法等效于 Class.forName(className, true, loader)。
这两个方法,最后都要连接到原生方法forName0(),其定义如下:
private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException;
只有一个参数的forName()方法,最后调用的是:
forName0(className, true, ClassLoader.getCallerClassLoader());
而三个参数的forName(),最后调用的是:
forName0(name, initialize, loader);
所以,不管使用的是new 來实例化某个类、或是使用只有一个参数的Class.forName()方法,内部都隐含了“载入类 + 运行静态代码块”的步骤。而使用具有三个参数的Class.forName()方法时,如果第二个参数为false,那么类加载器只会加载类,而不会初始化静态代码块,只有当实例化这个类的时候,静态代码块才会被初始化,静态代码块是在类第一次实例化的时候才初始化的。

    (2) 直接使用类加载器
           +— 获得对象所属的类 : getClass()方法
           |
           +— 获得该类的类加载器 : getClassLoader()方法

java 代码
 
  1. public class Main3 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         Main3 main3 = new Main3();  
  4.         System.out.println("准备载入类");  
  5.         ClassLoader loader = main3.getClass().getClassLoader();  
  6.         Class clazzA = loader.loadClass(args[0]);  
  7.         System.out.println("实例化类A");  
  8.         A o1 = (A) clazzA.newInstance();          
  9.     }  
  10. }  


3  类加载器的层次
                                          

 

 

 

 

 

 

 

我们可以通过java.net.URLClassLoader这个类构建一个自己的类加载器,来载入自己所需要的类。
下面是JDK中的说明:

public class URLClassLoader
extends SecureClassLoader
该类加载器用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。这里假定任何以 '/' 结束的 URL 都是指向目录的。如果不是以该字符结束,则认为该 URL 指向一个将根据需要打开的 JAR 文件。

当启动JVM的时候,可以使用三个类加载器:引导(bootstrap)类加载器、扩展(extensions)类加载器、应用程序(application)类加载器。

1.引导类加载器仅仅负责加载核心的Java库,比如位于<JAVA_HOME>/jre/lib 目录下的vm.jar,core.jar。这个类加载器,是JVM核心部分,是用native代码写成的。

2. 扩展类加载器负责加载扩展路径下的代码,一般位于<JAVA_HOME>/jre/lib/ext  或者通过java.ext.dirs 这个系统属性指定的路径下的代码。这个类加载器是由sun.misc.Launcher$ExtClassLoader 实现的。

3.应用程序类加载器负责加载java.class.path(映射系统参数 CLASSPATH的值) 路径下面的代码,这个类加载器是由 sun.misc.Launcher$AppClassLoader 实现的。

String bootClassPath = System.getProperty("sun.boot.class.path");

String extClassPath = System.getProperty("java.ext.dirs");

String appClassPath = System.getProperty("java.class.path");
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lihe2008125/archive/2009/02/05/3864983.aspx

开发者通常会使用如下语法通过类加载器机制加载属性文件:

Properties p = new Properties();

p.load(MyClass.class.getClassLoader().getResourceAsStream("myApp.properties"

));

这个意思是:如果MyClass 由扩展类加载器加载,而 myApp.properties 文件只能应用程序类加载器看到,则装入属性文件就会失败。

=========================

jvm classLoader architecture :

a, Bootstrap ClassLoader/启动类加载器
       主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作.

b, Extension ClassLoader/扩展类加载器
       主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作

c, System ClassLoader/系统类加载器
        主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作.

b, User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
        在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性.

类加载器的特性:

1, 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
2, 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构.
如下图:

 

Class Diagram:


类图中, BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。
因为, 它已经完全不用java实现了。

它是在jvm启动时, 就被构造起来的, 负责java平台核心库。(具体上面已经有介绍)

启动类加载实现 (其实我们不用关心这块, 但是有兴趣的, 可以研究一下 ):

bootstrap classLoader 类加载原理探索
www.iteye.com/topic/136885

自定义类加载器加载一个类的步骤 :

 

 

 


ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:

java 代码
  1. // 检查类是否已被装载过
  2. Class c = findLoadedClass(name);
  3. if (c == null ) {
  4.      // 指定类未被装载过
  5.      try {
  6.          if (parent != null ) {
  7.              // 如果父类加载器不为空, 则委派给父类加载
  8.              c = parent.loadClass(name, false );
  9.          } else {
  10.              // 如果父类加载器为空, 则委派给启动类加载加载
  11.              c = findBootstrapClass0(name);
  12.          }
  13.      } catch (ClassNotFoundException e) {
  14.          // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其
  15.          // 捕获, 并通过findClass方法, 由自身加载
  16.          c = findClass(name);
  17.      }
  18. }

用Class.forName加载类
Class.forName使用的是被调用者的类加载器来加载类的.
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰.
即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的.

java 代码
  1. public static Class forName(String className)
  2.      throws ClassNotFoundException {
  3.      return forName0(className, true , ClassLoader.getCallerClassLoader());
  4. }

  5. /** Called after security checks have been made. */
  6. private static native Class forName0(String name, boolean initialize,
  7. ClassLoader loader)
  8.      throws ClassNotFoundException;


上图中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器


线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader).

java 代码
  1. // Now create the class loader to use to launch the application
  2. try {
  3.     loader = AppClassLoader.getAppClassLoader(extcl);
  4. } catch (IOException e) {
  5.     throw new InternalError(
  6. "Could not create application class loader" );
  7. }

  8. // Also set the context class loader for the primordial thread.
  9. Thread.currentThread().setContextClassLoader(loader);


以上代码摘自sun.misc.Launch的无参构造函数Launch()。

使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).

线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.
使java类加载体系显得更灵活.

随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择.

当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException).

自定义的类加载器实现
defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)
是java.lang.Classloader提供给开发人员, 用来自定义加载class的接口.

使用该接口, 可以动态的加载class文件.

例如,
在jdk中, URLClassLoader是配合findClass方法来使用defineClass, 可以从网络或硬盘上加载class.

而使用类加载接口, 并加上自己的实现逻辑, 还可以定制出更多的高级特性.

比如,


一个简单的hot swap 类加载器实现:

java 代码
  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.lang.reflect.Method;
  4. import java.net.URL;
  5. import java.net.URLClassLoader;

  6. /**
  7. * 可以重新载入同名类的类加载器实现
  8. *

     

  9. * 放弃了双亲委派的加载链模式.
  10. * 需要外部维护重载后的类的成员变量状态.
  11. *
  12. * @author ken.wu
  13. * @mail ken.wug@gmail.com
  14. * 2007-9-28 下午01:37:43
  15. */
  16. public class HotSwapClassLoader extends URLClassLoader {

  17.     public HotSwapClassLoader(URL[] urls) {
  18.         super (urls);
  19.     }

  20.     public HotSwapClassLoader(URL[] urls, ClassLoader parent) {
  21.         super (urls, parent);
  22.     }

  23.     public Class load(String name)
  24.           throws ClassNotFoundException {
  25.         return load(name, false );
  26.     }

  27.     public Class load(String name, boolean resolve)
  28.           throws ClassNotFoundException {
  29.         if ( null != super .findLoadedClass(name))
  30.             return reload(name, resolve);

  31.         Class clazz = super .findClass(name);

  32.         if (resolve)
  33.             super .resolveClass(clazz);

  34.         return clazz;
  35.     }

  36.     public Class reload(String name, boolean resolve)
  37.           throws ClassNotFoundException {
  38.         return new HotSwapClassLoader( super .getURLs(), super .getParent()).load(
  39.             name, resolve);
  40.     }
  41. }

 

java 代码
  1. public class A {
  2.     private B b;

  3.     public void setB(B b) {
  4.          this .b = b;
  5.     }

  6.     public B getB() {
  7.          return b;
  8.     }
  9. }

 

java 代码
  1. public class B {}



这个类的作用是可以重新载入同名的类, 但是, 为了实现hotswap, 老的对象状态
需要通过其他方式拷贝到重载过的类生成的全新实例中来。(A类中的b实例)

而新实例所依赖的B类如果与老对象不是同一个类加载器加载的, 将会抛出类型转换异常(ClassCastException).

为了解决这种问题, HotSwapClassLoader自定义了load方法. 即当前类是由自身classLoader加载的, 而内部依赖的类还是老对象的classLoader加载的.

java 代码
  1. public class TestHotSwap {
  2. public static void main(String args[]) {
  3.     A a = new A();
  4.     B b = new B();
  5.     a.setB(b);

  6.     System.out.printf("A classLoader is %s \n" , a.getClass().getClassLoader());
  7.     System.out.printf("B classLoader is %s \n" , b.getClass().getClassLoader());
  8.     System.out.printf("A.b classLoader is %s \n" ,   a.getB().getClass().getClassLoader());

  9.     HotSwapClassLoader c1 = new HotSwapClassLoader( new URL[]{ new URL( "file:\\e:\\test\\")} , a.getClass().getClassLoader());
  10.     Class clazz = c1.load(" test.hotswap.A ");
  11.     Object aInstance = clazz.newInstance();

  12.     Method method1 = clazz.getMethod(" setB ", B.class);
  13.     method1.invoke(aInstance, b);

  14.     Method method2 = clazz.getMethod(" getB ", null);
  15.     Object bInstance = method2.invoke(aInstance, null);

  16.     System.out.printf(" reloaded A.b classLoader is %s \n", bInstance.getClass().getClassLoader());
  17. }
  18. }



输出

A classLoader is sun.misc.Launcher$AppClassLoader@19821f
B classLoader is sun.misc.Launcher$AppClassLoader@19821f
A.b classLoader is sun.misc.Launcher$AppClassLoader@19821f
reloaded A.b classLoader is sun.misc.Launcher$AppClassLoader@19821f

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    JVM
    jvm是jre里头一个动态连接函数库,jdk里面的jre一般用于运行java本身的程序,比如javac,等等.programfiles下面的jre用于运行用户编写的java程序.
    JRE下的bin\client 或者 bin\server 的jvm.dll就是JVM了

    当一台机器上有多个jvm可选择的时候,jvm的选择步骤:
    1)当前目录有没有jre目录(不准确),
    2)父目录下的jre子目录
    3)注册表HEKY_LOCAL_MACHINE\SoftWare\Java\Java Runtime Environment\
    所以当运行的是jdk\bin\java.exe的时候,用的jre是bin的父目录jdk下面的jre\
    运行java.exe找到了jre后有一个验证程序,验证jre和java.exe的版本是否一致,如果不一致则会发生错误

 

    java -verbose:class Main 显示调用的详细信息

 

    classloader的两种载入方式:1)pre-loading预先载入,载入基础类 2)load-on-demand按需求载入
    只有实例化一个类才会被classloader载入,仅仅申明并不会载入

 

    java动态载入class的两种方式:
    1)implicit隐式,即利用实例化才载入的特性来动态载入class
    2)explicit显式方式,又分两种方式:
      1)java.lang.Class的forName()方法
      2)java.lang.ClassLoader的loadClass()方法

 

    static块在什么时候执行?
    1)当调用forName(String)载入class时执行,如果调用ClassLoader.loadClass并不会执行.forName(String,false,ClassLoader)时也不会执行.
    2)如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
    3)static块仅执行一次

 


    Class类的实例.
    >>Class类无法手工实例化,当载入任意类的时候自动创建一个该类对应的Class的实例,
    >>某个类的所有实例内部都有一个栏位记录着该类对应的Class的实例的位置.,
    >>每个java类对应的Class实例可以当作是类在内存中的代理人.所以当要获得类的信息(如有哪些类变量,有哪些方法)时,都可以让类对应的Class的实例代劳.java的Reflection机制就大量的使用这种方法来实现
    >>每个java类都是由某个classLoader(ClassLoader的实例)来载入的,因此Class类别的实例中都会有栏位记录他的ClassLoader的实例,如果该栏位为null,则表示该类别是由bootstrap loader载入的(也称root laoder),bootstrap loader不是java所写成,所以没有实例.

    原生方法:forName0()等方法,native修饰符

 

    自定义ClassLoader:
    如实例化一个URLClassLoader. URLClassLoader ucl = new URLClassLoader(new URL[]{new URL("file:/e:/bin/")}),URLClassLoader优先找当前目录,再在url中找.class加载.URL中别忘在最后加"/"表示目录

 

    各个java类由哪些classLoader加载?
    1)java类可以通过实例.getClass.getClassLoader()得知
    2)接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()获得实例)载入
    3)ClassLoader类由bootstrap loader载入

 

    ClassLoader hierachy:
    jvm建立->初始化动作->产生第一个ClassLoader,即bootstrap loader->bootstrap loader在sum.misc.Launcher类里面的ExtClassLoader,并设定其Parent为null->bootstrap loader载入sun.misc.Launcher$AppClassLoader,并设定其parent为ExtClassLoader(但是AppClassLoader也是由bootstrap loader所载入的)->AppClassLoader载入各个xx.class,xx.class也有可能被ExtclassLoader或者bootstrap loader载入.
    >>自定义的ClassLoader的.getParent()是AppClassLoader.parent和他的加载器并没有关系
    >>ExtClassLoader和AppClassLoader都是URLClassLoader的子类.AppClassLoader的URL是由系统参数java.class.path取出的字符串决定,而java.class.path由 运行java.exe时 的-cp或-classpath或CLASSPATH环境变量决定
    >>ExtClassLoader查找的url是系统变量java.ext.dirs,java.ext.dirs默认为jdk\jre\lib\ext
    >>Bootstrap loader的查找url是sun.boot.class.path
    >>在程序运行后调用System.setProperty()来改变系统变量并不能改变以上加载的路径,因为classloader读取在System.setProperty之前.sun.boot.class.path是在程序中写死的,完全不能修改

    委派模型
    classloader有类需要载入时先让其parent搜寻其搜寻路径帮忙载入,如果parent找不到,在由自己搜寻自己的搜寻路径载入,ClassLoader hierachy本来就有这种性质

 


    NoClassDefFoundError和ClassNotFoundException
    NoClassDefFoundError:当java源文件已编译成.class文件,但是ClassLoader在运行期间在其搜寻路径load某个类时,没有找到.class文件则报这个错
    ClassNotFoundException:试图通过一个String变量来创建一个Class类时不成功则抛出这个异常
分享到:
评论

相关推荐

    黑马程序员------类加载器学习注意点

    Java的类加载器体系采用的是双亲委派模型,这意味着当一个类加载器接收到加载类的请求时,它会首先将任务委托给父类加载器,只有当父类加载器无法找到该类时,子类加载器才会尝试自己加载。 其次,自定义类加载器是...

    java 类加载器学习笔记1

    Java 类加载器是Java虚拟机(JVM)的重要组成部分,它负责将类的.class文件从文件系统或网络中加载到内存中,并将其转换为运行时的类对象。类加载器的这种动态加载机制为Java提供了极高的灵活性,使得程序可以在运行...

    java基础学习笔记之类加载器

    Java 类加载器是Java虚拟机(JVM)的关键组成部分,它们负责在运行时动态加载所需的类。类加载器基于三个核心机制:委托、可见性和单一性。 1. **委托机制**: - 当一个类加载器收到加载类的请求时,它首先不会...

    深入Java虚拟机JVM类加载学习笔记

    - **应用类加载器(Application ClassLoader)**:也称为系统类加载器,负责加载用户类路径(ClassPath)所指定的类。 **1.2 双亲委派模型** Java类加载器采用双亲委派模型,其流程如下: 1. 如果一个类加载器收到...

    类的加载机制笔记

    3. **应用程序类加载器(Application ClassLoader)**:也称为系统类加载器,它是ClassLoader类的默认实例,主要负责加载当前应用程序的类路径(ClassPath)所指定的jar或类文件。开发者可以创建自己的类加载器继承...

    Java虚拟机JVM类加载学习笔记

    JVM的类加载器(ClassLoader)负责将编译后的`.class`文件加载到内存中,为程序执行做好准备。当类被加载时,JVM会在内存的运行时数据区的方法区内存储类的信息,并在堆中创建一个`java.lang.Class`对象来封装这些...

    java读书笔记笔记笔记笔记笔记笔记

    综上所述,这份Java读书笔记可能涵盖了JVM的工作原理,特别是类的加载和执行过程,以及this关键字在代码中的应用。通过阅读Execution.htm和初始化总结this关键字.ppt,读者可以深化对Java程序执行流程和对象初始化的...

    jvm视频及笔记

    5. **类加载器**:系统类加载器、扩展类加载器和应用程序类加载器之间的双亲委派模型,以及自定义类加载器的实现。 6. **JVM调优**:通过调整JVM参数,如-Xms、-Xmx设置堆大小,-XX:NewRatio设定新生代与老年代比例...

    JVM学习笔记(一)——类的加载机制

    类加载机制遵循双亲委派模型,即当一个类加载器收到加载类的请求时,它首先会委托父类加载器尝试加载,只有当父类加载器无法加载时,子类加载器才会尝试加载。这样可以避免类的重复加载,保证核心类库的唯一性。 4...

    jvm学习笔记(jvm内存模型&垃圾收集算法&类加载机制)

    在JVM的学习中,理解其内存模型、垃圾收集算法以及类加载机制至关重要。 1. **JVM内存模型** - **方法区**:也称为“永久代”,存储虚拟机加载的类信息、常量、静态变量等,是线程共享的区域。在Java 8之后,这...

    火狐浏览器不支持有道云笔记网页版

    火狐浏览器不支持有道云笔记网页版的问题可能是由于浏览器兼容性、JavaScript 文件加载、样式表加载或浏览器插件问题引起的。用户可以尝试使用其他浏览器、禁用浏览器插件或使用客户端软件来解决问题。

    java之jvm学习笔记五(实践写自己的类装载器)

    这个“java之jvm学习笔记五(实践写自己的类装载器)”很可能是对这一主题的详细探讨。 类装载器在Java中的主要职责是动态加载类到JVM中。Java的类装载器分为三个基本层次:启动类装载器(Bootstrap ClassLoader)、...

    java 学习笔记(经典)

    根据提供的文件信息,这里将对Java学习笔记中的关键知识点进行详细阐述,主要涉及Java语言的基础概念、面向对象编程思想的应用以及Java类加载器的工作原理等内容。 ### Java学习基础 Java是一种广泛使用的高级编程...

    linux笔记笔记笔记笔记

    这个过程包括配置内核选项、编译源码、安装内核模块和更新引导加载器。 7. **鸟哥私房菜Linux**:这是一本广受欢迎的Linux入门书籍,涵盖了Linux系统的方方面面,从基本命令到系统管理,适合初学者和有经验的管理员...

    c#笔记本类练习.rar

    4. 构造函数与初始化:`Notebook`类可能有一个构造函数,用于初始化实例并加载已有的笔记本数据。 5. 错误处理:在增删改查操作中,代码可能会包含适当的错误处理,以防止如文件不存在或数据格式错误等情况。 6. ...

    连接器和加载器beta2.pdf

    ### 连接器和加载器基础知识 #### 1. 连接器和加载器功能概述 连接器和加载器是计算机程序构建过程中不可或缺的两个部分。连接器(Linker)负责将编译后的代码模块(通常是对象文件)合并成一个可执行文件。加载器...

    张孝祥J2SE加强自学笔记(48-56)

    【张孝祥J2SE加强自学笔记(48-56)】主要涵盖了类加载器、代理类和动态类创建等Java核心知识点。 48、类加载器的问题实验分析: 在Java中,类加载器是负责查找并加载类的机制。在Web应用中,有多种类加载器,如...

    JVM笔记.docx

    类装载器分为启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(AppClassLoader),以及用户自定义的类加载器。 2. 运行时数据区:包括方法区(Method Area)、...

    jvm上篇笔记.md

    类加载器之间的继承关系遵循着“父委托模型”,即一个类加载器在加载类之前会先委托给父类加载器尝试加载,只有当父类加载器无法加载时,才会自行加载。 通过上述分析,我们可以看到JVM是如何通过类加载子系统来...

Global site tag (gtag.js) - Google Analytics