JVM初识之类加载器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Weixiaohuai/article/details/86506576

一、类加载器作用

所谓类加载器,就是将.class二进制字节码文件加载到内存中,并将这写静态数据转换为方法区中的运行时数据结构,同时在堆中生成一个代表该类的Class对象,作为方法区类数据访问的入口。

二、类缓存

标准的JAVA SE类加载器可以按要求查找类,一旦这个类被加载到类加载器中,它会维持缓存一段时间,但是JVM垃圾回收器可以回收这些Class类对象。


三、类加载器的分类

如下图,类加载器的层次结构图:

注意:这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的。

【a】引导类加载器:负责加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar或被-Xbootclasspath参数指定的路径中的内容),使用原生代码(C++)实现的,并没有继承java.lang.ClassLoader类,所以无法被Java程序直接引用。

【b】扩展类加载器:该加载器由sun.misc.Launcher$ExtClassLoader实现,用来加载Java的扩展类库或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),使用Java实现,所以可以在java程序直接调用扩展类加载器。

【c】应用类加载器:由sun.misc.Launcher$AppClassLoader来实现,负责加载用户类路径(ClassPath)所指定的类。一般来说,Java应用的类都是有应用类加载器加载的。

【d】自定义类加载器:开发人员可以通过继承java.lang.ClassLoader类来自定义类加载器,以满足特殊的需求。

四、ClassLoader类介绍

【a】作用:

根据一个类的全限定名来找到或者生成对应的.class二进制字节码文件,然后生成该类对应的java.lang.Class类对象,当然,除了加载字节码文件,ClassLoader还负责加载一些图像文件和资源文件等。

【b】相关API

ClassLoader常用API方法
返回值 方法及描述
ClassLoader getParent()     返回委托的父类加载器。
protected  Class<?> findClass(String name) 使用指定的二进制名称查找类。
 Class<?> loadClass(String name) 使用指定的二进制名称来加载类。
protected  Class<?> findLoadedClass(String name)  如果 Java 虚拟机已将此加载器记录为具有给定二进制名称的某个类的启动加载器,则返回该二进制名称的类。
protected  Class<?> defineClass(String name, byte[] b, int off, int len)  将一个 byte 数组转换为 Class 类的实例。
protected  void resolveClass(Class<?> c) 链接指定的类。
protected  URL findResource(String name)  查找具有给定名称的资源。
 URL getResource(String name) 查找具有给定名称的资源。
InputStream getResourceAsStream(String name) 返回读取指定资源的输入流。
 Enumeration<URL> getResources(String name) 查找所有给定名称的资源。
static ClassLoader getSystemClassLoader() 返回委托的系统类加载器
protected  String findLibrary(String libname) 返回本机库的绝对路径名。

示例:如何获取系统默认的类加载器以及类加载器的层级结构:

public class Test01 {
    public static void main(String[] args) {
        //获取系统默认的类加载器(应用类加载器)
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        //sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(systemClassLoader);

        //应用类加载器的父类是扩展类加载器
        //sun.misc.Launcher$ExtClassLoader@1540e19d
        System.out.println(systemClassLoader.getParent());

        //扩展类加载器的父类是引导类加载器,因为引导类加载器是用C++实现,在Java代码中获取不到该对象,所以返回null
        //null
        System.out.println(systemClassLoader.getParent().getParent());

    }
}

五、类加载机制

【a】代理模式:交给其他类加载器来加载指定的类。

【b】双亲委托机制:就是某个类加载器接收到加载类的请求的时候,先把加载请求交给其父类加载器加载,依次追溯,知道爷爷辈类加载器,如果父类加载器可以加载那么成功加载该类,如果父类加载器不能完成加载任务,那么自己才会去加载。

具体的加载流程:

  • 1、当ApplicationClassLoader(系统默认的类加载器)加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtensionClassLoader去完成。
  • 2、当ExtensionClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
  • 3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtensionClassLoader来尝试加载;
  • 4、若ExtensionClassLoader也加载失败,则会使用ApplicationClassLoader来加载,如果ApplicationClassLoader也加载失败,则会报出异常ClassNotFoundException。

双亲委托机制保证了Java核心类库的类型安全。假设我们自己定义了java.lang.String这个类:

package java.lang;

public class String {

    public String toString() {
        return "value";
    }
}

这时候,其实这个String类时无法使用的,这是因为双亲委托机制,加载java.lang.String这个类的时候,根据双亲委派机制,最终到Bootstrap ClassLoader引导类加载器的时候,因为引导类加载器主要负责加载Java核心类库,它发现里面有java.lang.String这个类,所以它并不会去加载我们自定义的java.lang.String这个类,而是加载的核心类库中java.lang.String类,这就保证了Java核心类库的安全。

  • 注意:双亲委托机制是代理模式的一种,并不是所有的类加载器都使用双亲委托机制,例如tomcat服务器类加载也是使用代码模式,但是它首次加载的时候首先自己尝试去加载,如果自己不能加载才会去找父类加载器加载,这与一般的双亲委托机制顺序是相反的。

【c】缓存机制:缓存机制保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。所以当修改了Class后,必须重启JVM,才会生效。

 

六、类加载方式

在Java中类加载的方式主要有三种:

【a】命令行启动由JVM初始化加载;

【b】通过Class.forName()反射API方法加载;

【c】通过ClassLoader.loadClass(String name)方法进行加载

下面通过一个示例说明一下三种方法加载类:

package com.wsh.jvm.classloader03;

public class TestClassLoad {

    static {
        System.out.println("init static block");
    }

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

【a】命令行加载

【b】Class.forName()与ClassLoader.loadClass(String name):

public class TestClassLoaderMethod {
    public static void main(String[] args) {
        try {
//            Class<?> clazz = Class.forName("com.wsh.jvm.classloader03.TestClassLoad");
            //使用Class.forName()方法会执行静态代码块进行初始化
            //init static block

            //如果指定initialize = false的话则不会进行执行静态代码块初始化
            Class.forName("com.wsh.jvm.classloader03.TestClassLoad", false, ClassLoader.getSystemClassLoader());

            ClassLoader classLoader = TestClassLoaderMethod.class.getClassLoader();
//            使用ClassLoader.loadClass()方法默认不会执行静态代码块进行初始化
            Class<?> clazz2 = classLoader.loadClass("com.wsh.jvm.classloader03.TestClassLoad");
            //执行了newInstance()方法之后会执行静态代码块
            //init static block
            clazz2.newInstance();

        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }
}

Class.forName()和ClassLoader.loadClass()、Class.forName(String name, boolean initialize, ClassLoader loader)区别:

  • Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,默认会执行类中的static态代码块进行初始化操作;
  • ClassLoader.loadClass():只是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才执行static静态代码块进行初始化操作;
  • Class.forName(String name, boolean initialize, ClassLoader loader):可通过控制initialize = true/false的值来决定是否进行初始化。

七、总结

本文主要总结了类加载器的分类、层次结构以及类加载过程中的双亲委托机制思想,这只是笔者的一些见解和认识,不对之处,敬请指点一二,希望对大家的学习有所帮助。

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/86506576
今日推荐