JVM类加载机制解析(一)

201 阅读5分钟

JVM类加载机制解析

一、java类运行的主要流程(以windows操作系统为例)


通过java命令运行一个java类的主要流程如下图所示:
java命令运行某个类的主要流程.png

二、类加载过程详解

1. 类加载过程


类加载过程分为以下几步:

(1)加载


大家都知道,生成的class字节码文件都是存在本地磁盘中的,而加载这一步就是把磁盘中的class字节码文件放到java虚拟机内存中。

(2)验证


在加载前对class字节码文件的正确性进行校验。

(3)准备


为类中的静态变量分配内存并赋默认值。

例如:
  public static int a = 1;             -> public static int a = 0;
  public static boolean b = true;      -> public static boolean b = false;
  public static Param c = new Param(); -> public static Param c = null; 
如果是final修饰的常量则直接赋值为指定值
  public static final int d = 1;       -> public static final int d = 1;
  

(4)解析


将符号引用转换为直接引用。

符号:类中的各个名词(例如:main()、String[] args等)
符号引用:类中的各类包,类,接口,字段,方法等元素的全限定名
直接引用:指向数据在虚拟机内容中地址的指针或句柄

相关概念后面会详细分析。

(5)初始化


将静态变量初始化为指定的值并执行静态块。

(6)使用


使用加载完成后的类。

(7)卸载


当类的所有实例都被回收、类对应的类加载器被回收、该类的对应没有任何引用以及无法通过反射访问该类时类会被卸载。

2.类加载时机


java的类加载可以算是懒加载,只有当真正使用到这个类的时候,才会去加载这个类。

public class TestLazyLoad {

    static {
        System.out.println("-------------load TestLazyLoad-------------");
    }

    public static void main(String[] args) {
        ClassForUse classForUse = new ClassForUse();
        ClassForInit classForInit = null;
    }
}

class ClassForUse {

    static {
        System.out.println("-------------load ClassForUse-------------");
    }

    public ClassForUse() {
        System.out.println("-------------init ClassForUse-------------");
    }
}

class ClassForInit {

    static {
        System.out.println("-------------load classForInit-------------");
    }

    public ClassForInit() {
        System.out.println("-------------init classForInit-------------");
    }
}

执行TestLazyLoad类的main方法后打印结果:
-------------load TestLazyLoad-------------
-------------load ClassForUse-------------
-------------init ClassForUse-------------
如果将第9行代码改为:ClassForInit classForInit = new ClassForInit(),则打印结果为:
-------------load TestLazyLoad-------------
-------------load ClassForUse-------------
-------------init ClassForUse-------------
-------------load classForInit-------------
-------------init classForInit-------------
由此可以看出,当只声明了ClassForInit类时,该类并没有被加载,
只有当初始化了ClassForinit即使用了该类时,该类才会被加载。


三、类加载器

1. 类加载器的分类

  • 引导类加载器:加载支撑JVM运行的位于JRE的lib目录中的核心类库或-Xbootclasspath参数指定的路径
  • 扩展类加载器:加载支撑JVM运行的位于JRE的lib目录下的ext目录中的jar包或系统变量java.ext.dirs指定的路径
  • 应用程序类加载器:加载ClassPath下的类(自己写的类)
  • 自定义类加载器:加载自定义路径的类

前三个类加载器都是JDK提供的,可以通过下面的例子来看看JDK提供的类加载器的具体内容:

public class TestClassLoader {

    public static void main(String[] args) {

        // 引导类加载器
        System.out.println(String.class.getClassLoader());
        // 扩展类加载器
        System.out.println(ZipDirectoryStream.class.getClassLoader());
        // 应用程序类加载器
        System.out.println(TestClassLoader.class.getClassLoader());
    }
}

执行TestClassLoader的main方法会输出内容为:
null
sun.misc.Launcher$ExtClassLoader@a15670a
sun.misc.Launcher$AppClassLoader@1c898b41
因为引导类加载器是由c++实现的,所以第一行显示null
由此可以得出:
java中使用null表示引导类加载器
用ExtClassLoader表示扩展类加载器
用AppClassLoader表示应用程序类加载器

2. 类加载器初始化过程


可以通过跟踪sun.misc.Launcher来立即类加载器的初始化过程。

由文章开头部分的总流程图可以看出是由c++调用java代码创建sun.misc.Launcher,然后再创建其他类加载器
下面我们通过查看sun.misc.Launcher的源码来解析创建其他类加载器的过程:

Launcher类中有一个静态变量:
private static Launcher launcher = new Launcher();
说明Launcher使用了单例模式,保证整个JVM虚拟机里只有一个Lanucher实例

然后查看Lanucher的构造方法:
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader");
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader");
        }

        // ...省略
    }
由构造方法可以看出,Launcher在初始化时创建了ExtClassLoader和AppClassLoader两个类加载器。

先跟踪创建ExtClassLoader的过程:
Launcher.ExtClassLoader.getExtClassLoader() ->
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
  	
    // 跟踪getExtDirs方法可得核心代码:String var0 = System.getProperty("java.ext.dirs");
    // 即证明了ExtClassLoader加载的就是System.getProperty("java.ext.dirs")对应目录下的类
    final File[] var0 = getExtDirs();  
    // ...省略
    return new Launcher.ExtClassLoader(var0);
    // ...省略
}   

Launcher.ExtClassLoader(var0) ->
public ExtClassLoader(File[] var1) throws IOException {
    // 此处第二个参数如果继续跟踪会发现最终是赋值为ExtClassLoader的父类
    // 而null对应的类加载器在前文已经分析可以表示为引导类加载器
    super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
}
// ...省略调用父类构造方法的过程
private ClassLoader(Void unused, ClassLoader parent) {
    // 此处的parent就对应上面的(ClassLoader) null,也就是引导类加载器
    // 说明ExtClassLoader的父类是引导类加载器
    this.parent = parent;
    // ...省略
}
再跟踪创建AppClassLoader的过程:
Launcher.AppClassLoader.getAppClassLoader(var1) ->
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
    
    // 证明了AppClassLoader加载的就是System.getProperty("java.class.path")对应目录下的类
    final String var1 = System.getProperty("java.class.path");
    // ...省略
    return new Launcher.AppClassLoader(var1x, var0);
    // ...省略
}   

Launcher.AppClassLoader(var1x, var0) ->
AppClassLoader(URL[] var1, ClassLoader var2) {
		
    // 此处调用父类的构造方法和ExtClassLoader调用的父类构造方法一样,后面就不继续跟踪了
    // 而此处的var2就是上面传入的之前创建的ExtClassLoader
    // 说明AppClassLoader的父类是ExtClassLoader
    super(var1, var2, Launcher.factory);
}

总上所述,c++调用java代码初始化Launcher时也创建了ExtClassLoader和AppClassLoader,并且
也维护了两者的父类关系即ExtClassLoader的父类为引导类加载器,
AppClassLoader的父类为ExtClassLoader

下面可以通过一个例子来查看三个类加载器之间的关系:

public class TestClassLoaderParent {

    public static void main(String[] args) {
        
        // 从ClassLoader.getSystemClassLoader()源码中可得
        // 该方法返回值为Launcher中的loader属性,
        // 从Launcher的构造方法又可以得到,Launcher中的loader属性就是AppClassLoader
        // 所以此处用ClassLoader.getSystemClassLoader()来获取当前JVM中的AppClassLoader
        java.lang.ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        java.lang.ClassLoader extClassLoader = appClassLoader.getParent();
        java.lang.ClassLoader bootStrapClassLoader = extClassLoader.getParent();

        System.out.println("         appClassLoader:" + appClassLoader);
        System.out.println("appClassLoader's parent:" + extClassLoader);
        System.out.println("extClassLoader's parent:" + bootStrapClassLoader);
    }
}

运行代码输出的结果为:
         appClassLoader:sun.misc.Launcher$AppClassLoader@4921a90
appClassLoader's parent:sun.misc.Launcher$ExtClassLoader@140de648
extClassLoader's parent:null

跟上文的结论完全符合