JVM 之类加载器

Posted lyuzt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM 之类加载器相关的知识,希望对你有一定的参考价值。

一、什么是 JVM

  JVM(Java Virtual Machine)是一个可以执行 Java 字节码文件(即 .class 文件)的虚拟机进程。当 Java 源文件能被成功编译成 .class 文件,就能在不同平台上的不同版本的 JVM 运行,因为 JVM 能将相同的 .class 文件解释称不同平台的机器码。正是因为 JVM 的存在,Java 被称为与平台无关的语言。

  一般而言,.java 文件经过编译后会得到 .class 文件,而将这个文件加载到内存之前需要先通过类加载器,先简单过一下图:

            技术图片

二、类加载过程

  类加载的过程为: 加载-->连接(验证-->准备-->解析)-->初始化。下面介绍其中的几个过程。

 1、加载

  这个过程主要是通过类的全限定名,例如 java.lang.String 这样带上包路径的类名,获取到字节码文件;然后将这个字节码文件代表的静态存储结构(可简单理解为对象创建的模板)存在方法区,并在堆中生成一个代表此类的 Class 类型的对象,作为访问方法区中“模板”的入口,往后创建对象的时候就按照这个模板创建。

  举个例子,有时候通过反射创建对象,像当初学 JDBC 时会通过 Class.getName("com.mysql.jdbc.Driver.class").newInstance() 创建对象,通过 Class 和相应的全限定类名获取到方法区中的“模板”然后创建对象。

        技术图片

 2、验证

  验证过程主要确保被加载的类的正确性。首先要先验证文件格式是否规范,如果只是通过 .class 后缀来辨别,那随便把后缀名改一下就可以跑程序了,那岂不是很容易出事。来看看字节码文件大概是长什么样的:

       技术图片

 

  注意看前缀 cafe babe(咖啡宝贝?)这只是验证的其中一个点,还会验证字节码文件里是否包含主次版本号等验证信息。

 3、准备

  这个阶段主要是给类变量(静态变量)分配方法区的内存并初始化。实例变量不是在这个阶段分配内存,实例变量是随着对象一起分配在堆中。另外,给静态变量初始化为零值或空值,比如public static int n=5;这里并不是马上给 n 这个变量赋值为 5,而是先将其赋值为 0,类似的,如果是引用数据类型,则默认为 null。还有一点需要注意的是,对于 final 类型的数据,必须在程序内给它赋值,系统不会自动初始化,例如 static String str = "hello" + “world”;String 是 final 类型的,在编译阶段就给它优化成 static String str = "helloworld” ,并且将 "helloworld" 放进了常量池。

 4、初始化

  这个阶段就是将静态变量赋值为初始值,还是 public static int n=5; 这回给 n 赋值为 5 了。

三、类加载器

        技术图片

  启动类加载器是由C/C++写的,主要负责加载 jrelib 目录下的类;扩展类加载器主要负责加载 jrelibext 目录下的类;而应用程序类加载器主要负责加载我们自己编写的类;当然还能自己写类加载器,即自定义加载器。程序主要由前面三个类加载器相互配合加载的。

public class Main {
    public static void main(String[] args) {
        Main main = new Main();
        System.out.println(main.getClass().getClassLoader());
        System.out.println(main.getClass().getClassLoader().getParent());
        System.out.println(main.getClass().getClassLoader().getParent().getParent());
    }
}  

  技术图片由于启动类加载器是 C/C++ 语言写的,所以输出为 null

 双亲委派机制

  在类加载的过程中,存在着双亲委派机制,即当要加载一个类时,先由父类加载器加载,当父类加载器没办法加载时,才由下面的加载器加载,来看一个程序:

package java.lang;   // 自定义的包

public class String {
    public static void main(String[] args) {
        System.out.println("这是自定义的java.lang.String类");
    }
}

  技术图片

  由于 jrelibext 中存在 java.lang.String 类,当加载该类的时候,根据全限定名进行查找,找到后由启动类加载器加载,发现 String 类中不包含 main() 方法,因此程序出错。 

以上是关于JVM 之类加载器的主要内容,如果未能解决你的问题,请参考以下文章

JVM进阶之类加载器详解

JVM 之类加载器

深入JVM系列之类加载类加载器双亲委派机制与常见问题

JVM之类加载器加载过程及双亲委派机制

JVM之类加载器加载过程及双亲委派机制

JVM之类加载classLoader