专栏首页WriteOnReadJVM笔记-类加载机制

JVM笔记-类加载机制

JVM 不和包括 Java 在内的任何语言绑定,它只与 "Class文件" 这种特定的二进制文件格式所关联。而 Class 文件也并非只能通过 Java 源文件编译生成,可以通过如下途径而来:

JVM 把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称为虚拟机的「类加载机制」。

即Class 文件中描述的关于类的信息最终要加载到 JVM 中才能被运行和使用。

1. 类加载的时机

1.1 类的生命周期

一个类型(类或接口)从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期会经历加载(Loading)、验证(Verification)、准备(Prepare)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析统称为连接(Linking)。如图所示:

1.2 初始化时机

JVM 规范对于“加载”阶段并未强制约束。但对于“初始化”阶段,则规定有且仅有以下六种情况必须立即对其“初始化”:

  1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时。场景如下:
    1. 使用 new 关键字实例化对象;
    2. 读/写静态字段(static 修饰,无 final);
    3. 调用静态方法。
  2. 使用 java.lang.reflect 的方法对类型进行反射调用时。
  3. 初始化类时,若父类尚未初始化,需要先初始化其父类。
  4. 虚拟机启动时,需要先初始化用户指定的主类(main 方法所在类)。
  5. 使用 JDK 7 新加入的动态语言支持时,若一个 java.lang.invoke.MethodHandle 实例最后解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial 四种类型的方法句柄,且该方法句柄对应的类未初始化,需要先初始化【平时似乎没用到过,暂不深究,以后有机会再分析】。
  6. 接口中定义了 JDK 8 加入的默认方法(default 修饰)时,在该接口的实现类初始化之前,需要先初始化这个接口。

注意:当一个“类”在初始化时,要求其父类全都已经初始化;但是,一个“接口”在初始化时,并不要求父接口全都初始化,只有真正使用到父接口时才会初始化(比如引用接口定义的常量)。

1.3 主动引用&被动引用

上述六种情况的行为称为对一个类型的“主动引用”,而除此之外的其他所有引用类型方式都不会触发初始化,称为“被动引用”。被动引用举例如下:

  • 示例代码
public class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }

    public static int value = 123;
    public static final String HELLO_WORLD = "hello, world";
}

public class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init!");
    }
}

PS: 为了跟踪类加载信息,可配置虚拟机参数 -XX:+TraceClassLoading

  • eg1
/**
 * 通过子类引用父类的静态字段,不会导致子类初始化
 */
public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
    }
}

/* 类加载情况:SubClass 和 SuperClass 均被加载
 * 
 * 输出结果(父类初始化,子类未初始化):
 * SupClass init!
 * 123
 */
  • eg2
/**
 * 通过数组定义来引用类,不会触发此类的初始化
 */
public class NotInitialization {
    public static void main(String[] args) {
        SuperClass[] superClasses = new SuperClass[10];
    }
}

/* 类加载情况:SuperClass 被加载
 * 输出结果为空,SuperClass 未初始化 
 */
  • eg3
/**
 * 常量在【编译阶段】会存入调用类(NotInitialization)的常量池中,
 * 本质上并没有直接引用到定义常量的类,因此不会触发其初始化
 */
public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(SuperClass.HELLO_WORLD);
    }
}

/* 类加载情况:SubClass 和 SuperClass 均未被加载
 *
 * 输出结果:
 * hello, world
 */

编译阶段通过常量传播优化,已将该常量的值("hello, world")直接存储在 NotInitialization 类的常量池中,以后 NotInitialization 对常量 SuperClass.HELLO_WORLD 的引用实际都被转化为对自身常量池的引用了。

PS: 其实 NotInitialization 类的 Class 文件中并不存在 SuperClass 类的符号引用入口,这两个类在编译成 Class 文件之后就没联系了。

2. 类加载过程

2.1 加载

加载阶段,JVM 主要做了三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流;
  2. 将该字节流所代表的静态存储结构转化为方法区的运行时数据结构;
  3. 在(堆)内存中生成一个代表该类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

PS: 二进制字节流的来源有很多,例如:从 ZIP 压缩包读取、从网络获取、运行时计算生成(动态代理),从加密文件读取等。

需要注意的是,数组类的加载情况有所不同:数组类本身不通过类加载器创建,而是由 JVM 直接在内存动态构造(newarray 指令)。它的创建过程遵循以下原则:

  • 若数组的组件类型(数组去掉一个维度)为引用类型,则递归加载该组件类型;
  • 若数组的组件类型不是引用类型(例如 int[] 组件类型为 int),JVM 会把数组标记为与引导类加载器关联;
  • 数组类的可访问性与其组件类型的可访问性一致(若组件类型不是引用类型,可访问性默认为 public)。

2.2 验证

主要目的:确保 Class 文件信息符合 JVM 规范,防止恶意代码危害虚拟机自身安全。

有点类似我们平时开发接口时的参数校验,不能因为入参问题把程序搞崩溃了。

该阶段大致会完成下面四个阶段的验证:文件格式验证、元数据验证、字节码验证和符号引用验证。

2.2.1 文件格式验证

验证字节流是否符合 Class 文件格式的规范,且能被当前虚拟机处理。主要验证:

  • 是否以魔数 0xCAFEBABY 开头;
  • 主次版本号是否在当前虚拟机处理范围内;
  • ……

PS: 该阶段是基于二进制字节流进行的,验证通过之后才允许进入 JVM 的方法区。而后面的验证都是基于方法区的存储结构进行的,不再直接读取字节码。

2.2.2 元数据验证

对类的元数据信息进行语义校验,确保不违背 Java 语言规范。比如:

  • 一个类是否有父类;
  • 该父类是否继承了 final 修饰的类;
  • ……
2.2.3 字节码验证

该阶段最复杂,主要是数据流分析和控制流分析,确定语义合法、符合逻辑。验证点如下:

  • 操作数栈的数据类型与指令代码序列能配合工作;
  • 跳转指令不会跳到方法体以外的字节码指令上;
  • 类型转换有效;
  • ……
2.2.4 符号引用验证

发生在虚拟机将符号引用转为直接引用时(即后面的解析阶段),确保解析动作能正常执行。验证点如下:

  • 符号引用中通过字符串描述的全限定名是否能找到对应的类;
  • 符号引用中的类、字段、方法的可访问性验证;
  • ……

验证阶段虽然很重要,却并非必须执行。若程序代码已被反复使用和验证,可以考虑关闭大部分类验证,以缩短类加载的时间。JVM 参数:

-Xverify:none

2.3 准备

主要目的:为类变量(即 static 修饰的静态变量)分配内存并设置初始值。

初始值“通常”情况指的是零值,基本数据类型的零值如下:

// 经过「准备」阶段后,该初始值为 0
// 而把 value 赋值为 123 是在后面的「初始化」阶段
public static int value = 123;

注意,上面的“通常”不包含一种情况,即静态变量被 final 修饰的时候,例如:

public static final int value = 123;

编译阶段会为 value 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 将其设置为 123.

2.4 解析

主要动作:把常量池内的符号引用替换为直接引用。

该阶段主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行。

2.4.1 符号引用

符号引用(Symbolic References):以一组符号描述所引用的目标,可以是任何形式的字面量(比如全限定类名)。引用的目标不一定加载到 JVM 内存中。

  • 代码示例

比如有两个 java 文件,分别为 A.java 和 B.java,如下:

public class A {
}

public class B {
  private A a;
}

其中 B 持有对 A 的引用,但此时两个类并未加载到内存中,仅仅是一个标记而已。

2.4.2 直接引用

直接引用(Direct References)可以是:

  1. 直接指向目标的指针;
  2. 相对偏移量(例如实例变量、实例方法);
  3. 能间接定位到目标的句柄。

直接引用就是能够直接在内存中找到相应对象(的内存地址)。若有直接引用,则目标必定已在虚拟机中。

2.5 初始化

初始化阶段就是执行类构造器 <clinit>() 方法的过程,<clinit>() 方法有如下特点:

  • 由编译器根据源文件中的顺序、自动收集类中的所有静态变量的赋值动作和静态代码块合并产生。
  • 此方法与类的构造方法(虚拟机视角中的实例构造器 <init>() 方法,也就是我们在代码中定义的构造器)不同,不需要显式地调用父类构造器,JVM 会保证子类 <clinit>() 方法执行前,父类 <clinit>() 方法已执行完。

PS: 与类不同的是,接口的方法不需要先执行父接口的 <clinit>() 方法。 接口的实现类在初始化时也不会执行接口的 <clinit>() 方法。

  • 该方法并不是必需的,若类中无静态语句块和对变量的赋值操作,编译器可以不生成这个方法。

接口中虽然不能使用静态代码块,却可以为变量始化赋值,因此也会生成 <clinit>() 方法。

  • JVM 必须保证一个类的 <clinit>() 方法在多线程环境被正确地加锁同步。如果多个线程同时去初始化一个类,只能有一个线程去执行 <clinit>() 方法,其他线程都要阻塞等待。

说到这里,设计模式的「单例模式」就有一种写法是利用该机制来保证线程安全性的,示例代码如下:

public class BeanFactory {
  private BeanFactory() {
  }

  public BeanFactory getBeanFactory() {
    return BeanFactoryHolder.beanFactory;
  }

  /**
   * 使用内部嵌套类实现单例,利用 JVM 的类加载机制可保证线程安全
   */
  private static class BeanFactoryHolder {
    private static BeanFactory beanFactory = new BeanFactory();
  }
}

3. 类加载器

所谓类加载器(Class Loader),其实就是一段代码。

这段代码的主要功能就是:通过一个类的全限定名来获取描述类信息的二进制字节流。

3.1 类与类加载器

对于任意一个类,都必须由其「类加载器」和「该类本身」共同确定它在 JVM 中的唯一性。

即,若要比较两个类是否相等,前提是这两个类必须是由同一个类加载器加载(后面代码进行验证)。

PS: 这里的“相等”,包括 equals、isAssignableFrom、isInstance 等方法,还有 instanceof 关键字。

3.2 双亲委派模型

类加载器的分类及其主要特点如下:

  • 启动类加载器(Bootstrap Class Loader)
    • 虚拟机的一部分(C++ 实现);
    • 负责加载 JAVA_HOME\lib 目录,或者 -Xbootclasspath 参数指定路径下,且被 JVM 识别的类库。
  • 扩展类加载器(Extension Class Loader)
    • 由 sun.misc.Launcher$ExtClassLoader 类实现;
    • 负责加载 JAVA_HOME\lib\ext 目录,或者 java.ext.dirs 系统变量指定的路径中的类库。
  • 应用程序类加载器(Application Class Loader)
    • 由 sun.misc.Launcher$AppClassLoader 类实现;
    • 加载用户类路径(ClassPath)下所有的类库;
    • 默认的系统类加载器(若应用程序没有自定义过类加载器,一般使用该类进行加载)。

若有必要,还可以加入自定义的类加载器进行扩展。

JDK 9 之前的 Java 应用都是由这三种类加载器互相配合完成加载的。它们之间的协作关系如图所示:

这种层次关系被称为类加载器的“双亲委派模型(Parents Delegation Model)”。

双亲委派模型的工作流程大致如下:

若一个类加载器收到了加载类的请求,它首先不会自己尝试去加载这个类,而是将其委派给父类加载器,父加载器亦是如此,直至启动类加载器;仅当父加载器无法加载该类的时候,子加载器才会尝试自己进行加载。

注意:这里的它们之间并非「继承」关系,通常是采用「组合」的方式。

3.2.1 实现源码

双亲委派模型的实现代码在 java.lang.ClassLoader 类的 loadClass 方法中,如下:

private final ClassLoader parent;

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 先检查请求的类是否已加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 若未加载过,调用父类加载器进行加载(父类加载器也会继续该过程)
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 使用启动类加载器加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父类加载器无法完成加载
            }
            
            // 父类加载器未完成加载时,使用自身的 findClass 方法尝试加载
            if (c == null) {
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

// JDK 1.2 提供的
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}
3.2.2 优点

为什么要采用双亲委派模型?这样做有什么好处呢?

一个好处就是:Java 类随着类加载器有了层级关系,把最基础的类,例如 java.lang.Object,交给最顶端的类加载器加载,保证在各个加载器环境中都是同一个 Object 类。

说到这里,有些面试题会问:如果自定义一个 java.lang.Object 类会怎样?

  • 自定义 java.lang.Object 类

这里做下测试,自定义一个 java.lang.Object 类:

package java.lang;

public class Object {
  public String toString() {
    return "hello";
  }

  public static void main(String[] args) {
    System.out.println("hello");
  }
}

如果能正常加载,这里会打印字符串 "hello",结果呢?会报错:

Error: Main method not found in class java.lang.Object, please define the main method as:
   public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application

错误原因是 main 方法未找到,就是我们自定义的方法未找到。

查看类加载信息:

[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar]
...

可以发现,JVM 只加载了 rt.jar 中的 java.lang.Object ,并没有加载我们定义的这个 Object 类,而 rt.jar 中的 Object 是没有 main 方法的。

  • 自定义 java.lang.HelloWorld 类

如果我们定义一个全类名为 java.lang.HelloWorld 的类呢?

package java.lang;

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("hello");
  }
}

可以正常加载和运行吗?并不会!

异常如下:

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
  at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
  at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
  at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
  at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
  at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
  ...

可以看到,java.lang 这个包名是禁止使用的。

3.3 破坏双亲委派模型

PS: “破坏双亲委派模型“这个概念刚开始听起来可能有些费解,尤其是这个”破坏“,至少我是这样。 其实呢,双亲委派模型可以理解为一个规范,然鹅,某些地方由于某些原因并未遵循这个规范。对于那些没有遵循该规范的地方,就是破坏了双亲委派模型。

总的来说,破坏双亲委派模型的行为大致有三次:

  • 第一次

由于“双亲委派模型”是 JDK 1.2 引入的,但类加载和 java.lang.ClassLoader 类在此之前就已经存在了,为了兼容已有代码,双亲委派模型做了妥协。

由于 ClassLoader 类的 loadClass 方法可以直接被子类重写,这样的类加载机制就不符合双亲委派模型了。

如何实现兼容呢?在 ClassLoader 类添加了 findClass 方法(代码见 3.2.1),并引导用户重写该方法,而非 loadClass 方法。

这就是第一次破坏双亲委派模型,其实就是兼容历史遗留问题。

  • 第二次

双亲委派模型的类加载都是自底向上的(越基础的类由越上层的加载器来加载),但有些场景可能会出现基础类型要反回来调用用户代码,这个场景如何解决呢?

一个典型的例子就是 JNDI (启动类加载器加载)服务,其目的是调用其它厂商实现并部署在应用程序 ClassPath 下的服务提供者接口(Service Provider Interface,SPI)。启动类加载器是不认识这些 SPI 的,如何解决呢?

Java 团队引入了一个线程上下文类加载器(Thread Context ClassLoader),可以设置类加载器,在启动类加载器不认识的地方,调用其它类加载器去加载。这其实也打破了双亲委派模型。

比如 JDBC 的类加载机制,后文再详细分析。

  • 第三次

第三次破坏是对程序动态性的追求导致的,代码热替换(Hot Swap)、模块热部署(Hot Deployment)等。典型的如 IBM 的 OSGi 模块化热部署。

4. 代码示例

4.1 自定义类加载器

public class MyClassLoader extends ClassLoader {

  // 重写 findClass 方法
  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] classData = loadClassData(name);
    if (classData == null) {
      throw new ClassNotFoundException();
    }
    return defineClass(name, classData, 0, classData.length);
  }

  // 读取 class 文件
  private byte[] loadClassData(String className) {
    String fileName = "~/Code/Java/test/target/classes" +
        File.separatorChar + className.replace('.', File.separatorChar) + ".class";
    try {
      FileInputStream inputStream = new FileInputStream(fileName);
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int length;
      while ((length = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, length);
      }
      return outputStream.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }
}

4.2 双亲委派模型类加载

  • 自定义一个 Person 类
package loader;

public class Person {
  static {
    // 当 Person 类初始化时,会打印该代码
    System.out.println("Person init!");
  }

  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

使用上面自定义的类加载加载 Person 类:

private static void test1() throws Exception {
  // 创建类加载器实例
  MyClassLoader myClassLoader1 = new MyClassLoader();
  // 加载 Person 类(注意这里是 loadClass 方法)
  Class<?> aClass1 = myClassLoader1.loadClass("loader.Person");
  aClass1.newInstance(); // Person init!

  MyClassLoader myClassLoader2 = new MyClassLoader();
  Class<?> aClass2 = myClassLoader2.loadClass("loader.Person");
  aClass2.newInstance();
    
  System.out.println("--->" + aClass1.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2
  System.out.println("--->" + aClass2.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2
  System.out.println("--->" + aClass1.equals(aClass2)); // true
}

可以看到,这里虽然使用了两个类加载器实例加载 Person 类,但实际上 aClass1 和 aClass2 的类加载器并不是自定义的 MyClassLoader,而是 Launcher$AppClassLoader,即应用类加载器。为什么会是这个结果呢?

其实这就是前面分析的双亲委派模型,示意图如下:

大体流程分析:

  1. 使用 MyClassLoader 加载 Person 类时,它会先委托给 AppClassLoader;
  2. AppClassLoader 委托给 ExtClassLoader;
  3. ExtClassLoader 委托给启动类加载器;
  4. 但是,启动类加载器并不认识 Person 类,无法加载,于是就再反回来交给 ExtClassLoader;
  5. ExtClassLoader 也无法加载,于是交给了 AppClassLoader;
  6. AppClassLoader 可以加载 Person 类,加载结束。

4.2 非双亲委派模型类加载

上面演示了双亲委派模型加载一个类,如何破坏双亲委派模型呢?把上面的 loadClass 方法换成 findClass 就行,示例代码:

  • 测试类加载 eg.1
private static void test2() throws Exception {
  MyClassLoader cl1 = new MyClassLoader();
  // 加载自定义的 Person 类
  Class<?> aClass1 = cl1.findClass("loader.Person");
  // 实例化 Person 对象
  aClass1.newInstance(); // Person init!

  MyClassLoader cl2 = new MyClassLoader();
  Class<?> aClass2 = cl2.findClass("loader.Person");
  aClass2.newInstance(); // Person init!

  System.out.println("--->" + aClass1); // class loader.Person
  System.out.println("--->" + aClass2); // class loader.Person

  System.out.println("--->" + aClass1.getClassLoader()); // loader.MyClassLoader@60e53b93
  System.out.println("--->" + aClass2.getClassLoader()); // loader.MyClassLoader@1d44bcfa  
  
  System.out.println("--->" + aClass1.equals(aClass2)); // false
}

这里创建了两个自定类加载器 MyClassLoader 的实例,分别用它们来加载 Person 类。

虽然两个打印结果都是 class loader.Person ,但类加载器不同,导致 equals 方法的结果是 false,原因就是二者使用了不同的类加载器。

根据 MyClassLoader 的代码,这里实际并未按照双亲委派模型的层级结构去加载 Person 类,而是直接使用了 MyClassLoader 来加载的。

  • 测试类加载 eg.2

上述代码中,如果使用同一个类加载器进行加载呢?修改代码如下:

private static void test3() throws Exception {
  MyClassLoader cl1 = new MyClassLoader();
  Class<?> aClass1 = cl1.findClass("loader.Person");
  aClass1.newInstance();

  // 这里改用上面的类加载进行加载呢?
  Class<?> aClass2 = cl1.findClass("loader.Person");
  aClass2.newInstance();

  System.out.println("--->" + aClass1);
  System.out.println("--->" + aClass2);
  System.out.println("--->" + aClass1.equals(aClass2)); // true ??
}

这样的比较结果会是 true 吗?似乎应该是的吧。。

然而,这样会报错的:

Exception in thread "main" java.lang.LinkageError: loader (instance of  loader/MyClassLoader): attempted  duplicate class definition for name: "loader/Person"
  at java.lang.ClassLoader.defineClass1(Native Method)
  at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
  at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
  at loader.MyClassLoader.findClass(MyClassLoader.java:21)
  at loader.TestClassLoader.test1(TestClassLoader.java:61)
  at loader.TestClassLoader.main(TestClassLoader.java:10)

原因是:一个类加载器不能多次加载同一个类。

本文分享自微信公众号 - WriteOnRead(WriteOnRead),作者:jaxer

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JDK源码分析-DelayQueue

    DelayQueue 也是一种队列,它内部的元素有“延迟”,也就是当从队列中获取元素时,如果它的延迟时间未到,则无法取出。

    WriteOnRead
  • JDK源码分析-ArrayBlockingQueue

    前文「JDK源码分析-BlockingQueue」简要分析了 BlockingQueue 接口的主要方法,ArrayBlockingQueue 就是该接口的一个...

    WriteOnRead
  • JDK源码分析-LinkedHashMap

    前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap。

    WriteOnRead
  • [五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的

    本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的

    noteless
  • 第一章 类加载到卸载的全过程分析

     在Java代码中,类型的加、连接与初始化过程都是在程序运行期间完成的。其中类型指我们定义的一个class、interface、enum,此时并未包含对象。这一...

    Fisherman渔夫
  • 虚拟机类加载机制(3)——线程上下文类加载器

    之所以将线程上下文类加载器(Thread Context ClassLoader)单独拿出来写,确实是因为它涉及的东西比较多,既然带有线程两个字,一定也是非常重...

    用户1148394
  • java类的加载过程和类加载器的分析

    我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨...

    矿泉水
  • java类的加载过程和类加载器的分析

    我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨...

    用户2141593
  • 深入理解Java类加载机制

    其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。

    李红
  • Java类加载器详解(上)

    我们知道,新建一个Java对象的时候,JVM要将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath(就是我们新建Java工程的bin...

    Java团长

扫码关注云+社区

领取腾讯云代金券