关于java:jvm-类的加载

31次阅读

共计 2272 个字符,预计需要花费 6 分钟才能阅读完成。

jvm – 类的编译,提到了把本地机器码转变成字节码,以及编译的优化。那这些字节码文件是怎么到 JVM 的呢?

一个类从被加载到虚拟机内存中开始,到卸载出内存,它的整个生命周期包含:加载(Loading)、验证(Verification)、筹备(Preparation)、解析(Resolution)、初始化(Initialization)、应用(Using)和卸载(Unloading)7 个阶段。

在 Java 虚拟机中类加载的全过程,包含加载、验证、筹备、解析和初始化这 5 个阶段所执行的具体动作,这些都是有类加载器来实现的。

类加载

加载

加载是类加载过程的一个阶段。首先来一个简略的代码,打印 ### 以及创立一个 Hello 对象。

public class ClassLoad {public static void main(String[] args) {System.out.println("########################");
        Hello hello = new Hello();}
}

运行之前,设置 -XX:+TraceClassLoading

运行后果如下(截取前面局部),能够看到 com.jvm.load.ClassLoad 先被加载,而后是 com.jvm.cls.Hello。ClassLoad 是这个 main 办法的主类,所以优先加载。Hello 的加载,是在实例化的时候,也就是被用到的时候,如果读者本人去断点,那就更直观的看到了。

下面这个图,能够看到输入了类的全限定名,类加载器就是通过这个来获取它的二进制字节流,这个二进制字节流起源如下:

  • class 文件
  • zip、jar、war 包中读取
  • 网络中读取,比方 Applet
  • 运行时计算生成,比方动静代理技术
  • 由其余文件生成,比方 JSP

验证

验证是为了确保 Class 文件的字节流中蕴含的信息合乎以后虚拟机的要求,并且不会危害虚拟机本身的平安。当加载的 class 文件不合乎虚拟机的要求,java 虚拟机是无奈执行这个字节码的,所以要先看看有没有合乎,合乎了才给虚拟机执行后续操作。

筹备

筹备是正式为类变量分配内存并设置类变量初始值的阶段。也就是说 com.jvm.load.ClassLoadcom.jvm.cls.Hello在虚拟机中的内存调配是在这个阶段。这时候进行内存调配的仅包含类变量(被 static 润饰的变量),而不包含实例变量,实例变量将会在对象实例化时随着对象一起调配在 Java 堆中。设置类变量初始值通常状况下就是数据类型的零值。

// 筹备阶段 value=0
public static int value = 123;
// 筹备阶段 value2=123
public static final int value2 = 123;

解析

解析是虚拟机将常量池内的符号援用替换为间接援用的过程。
比方 com.jvm.load.ClassLoad 编译的时候,不晓得 com.jvm.cls.Hello 的理论内存地址,此时用符号援用,当 com.jvm.cls.Hello 加载到内存后,此时就改为间接援用,指向 Hello 的内存地位。

初始化

在筹备阶段 value=0,在初始化阶段,value 才赋值为 123。
类初始化的条件:

  1. new 一个对象,动态变量的赋值和取值,静态方法的调用。
  2. 通过反射机制调用。
  3. 如果子类初始化的时候,父类未初始化。
  4. 执行的主类(main 办法)的时候。

上面看看类尽管被加载,却没有初始化的例子。
SuperClass:

public class SuperClass {
    static {System.out.println("SuperClass init");
    }
    public static int value = 123;
}

SubClass:

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

ClassLoad:

public class ClassLoad {public static void main(String[] args) {System.out.println("########################");
        //Hello hello = new Hello();
 System.out.println(SubClass.value);
    }
}

运行后果如下:

能够看到 SubClass 被加载了,然而并没有输入SubClass init

类加载器

类加载器有这几个:

  • 启动类加载器:jvm 启动的时候,会优先加载 <JAVA_HOME>\lib 这个目录的外围类库。
  • 扩大类加载器:负责加载 <JAVA_HOME>\lib\ext 这个目录的类。
  • 应用程序类加载器:负责加载咱们写的代码。
  • 自定义类加载器:依据咱们的须要,加载特定的类。

下图展现了类加载器间接的档次关系,成为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都该当有本人的父类加载器。

他的工作过程是这样的:

  1. 应用程序类加载器收到了 Hello 类的加载申请,先问扩大类加载器是否能够加载。
  2. 扩大类加载器也不会间接去加载,他也是向下级启动类加载器询问是否能够加载。
  3. 启动类加载器在本人负责的目录搜寻了一下,发现自己找不到这个类,就说不行,你本人加载吧。
  4. 扩大类加载器在本人负责的目录搜寻了一下,发现自己找不到这个类,就说不行,你本人加载吧。
  5. 应用程序类加载器在本人负责的目录搜寻了一下,找到了这个类,把 Hello 类加载进来。


双亲委派模型一个不言而喻的益处就是 Java 类随着它的类加载器一起具备了一种带有优先级的档次关系。

正文完
 0