首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM:类加载器

JVM:类加载器

原创
作者头像
HLee
修改2021-08-31 11:26:54
8280
修改2021-08-31 11:26:54
举报
文章被收录于专栏:房东的猫房东的猫

类加载器

Java虚拟机设计团队有意把类加载阶段中的"通过一个类的全限定名来获取描述该类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何获取所需的类。实现这个动作的代码被称为"类加载器"(ClassLoader)。

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。每一个类加载器,都拥有一个独立的类名称空间。比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

顾名思义,类的加载器就是负责类的加载职责,对于任意一个class,都需要由加载它的类加载器和这个类本身确立其在JVM中的唯一性,这也就是运行时包。任何一个对象的class在JVM中只会存在唯一的一份,如String.class、Object.class在堆内存以及方法区中肯定是唯一的,但是不能绝对理解为我们自定义的类在JVM中同样也是。

JVM为我们提供了三大内置的类加载器,不同的类加载器负责将不同类加载到JVM内存之中,并且它们之间严格遵守父委托机制。

启动类加载器

启动类加载器又称为BootStrap类加载器,该类加载器是最为顶层的加载器,其没有任何父类加载器,它由C++编写,这个类加载器负责虚拟机核心类库的加载,比如整个java.lang包都是有启动类加载器所加载的,存放在<JAVA_HOME>\lib目录或是被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录下也不会被加载)类库加载到虚拟机内存中。

public class BootStrapClassLoader {

    public static void main(String[] args) {
        
        System.out.println("BootStrap:" + String.class.getClassLoader());
        System.out.println(System.getProperty("sun.boot.class.path"));
    }
}

输出:
BootStrap:null
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/sunrsasign.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/classes

上述程序输出结果显示,其中String.class的类加载器是启动类加载器,启动类加载器是获取不到引用的,因此输出为null,而启动类加载器所在的加载路径可以通过sun.boot.class.path这个系统属性来获得。

扩展类加载器

扩展类加载器的父加载器是根加载器,扩展类加载器是由纯Java语言编写,它是java.lang.URLClassLoader的子类,这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。

由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。

public class ExtClassLoader {

    public static void main(String[] args) {
        System.out.println(System.getProperty("java.ext.dirs"));
    }
}

运行上边的程序会得到扩展类加载器加载资源的路径,输出:
/Users/lihuan/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

系统类加载器

系统类加载器是一种常见的类加载器,它负责加载用户路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。系统类加载器的加载路径一般通过-classpath或者-cp指定,同样也可以通过系统属性java.class.path进行获取。

public class ApplicationClassLoader {

    public static void main(String[] args) {
        System.out.println(System.getProperty("java.class.path"));
        System.out.println(ApplicationClassLoader.class.getClassLoader());
    }
}

输出:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/deploy.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/dnsns.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/jaccess.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/localedata.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/nashorn.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunec.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/zipfs.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/javaws.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfxswt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/plugin.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/dt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/jconsole.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/packager.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/tools.jar:
/Users/lihuan/Documents/projects/git/me/spring-master/target/classes:/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter/2.3.2.RELEASE/spring-boot-starter-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot/2.3.2.RELEASE/spring-boot-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-autoconfigure/2.3.2.RELEASE/spring-boot-autoconfigure-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter-logging/2.3.2.RELEASE/spring-boot-starter-logging-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:
/Users/lihuan/Documents/opt/maven/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/logging/log4j/log4j-to-slf4j/2.13.3/log4j-to-slf4j-2.13.3.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/logging/log4j/log4j-api/2.13.3/log4j-api-2.13.3.jar:
/Users/lihuan/Documents/opt/maven/repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/lihuan/Documents/opt/maven/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-core/5.2.8.RELEASE/spring-core-5.2.8.RELEASE.jar:/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-jcl/5.2.8.RELEASE/spring-jcl-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar:/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-suggest/5.5.2/lucene-suggest-5.5.2.jar:/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-analyzers-common/5.5.2/lucene-analyzers-common-5.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-core/5.5.2/lucene-core-5.5.2.jar:/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-misc/5.5.2/lucene-misc-5.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-queries/5.5.2/lucene-queries-5.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-grouping/5.5.2/lucene-grouping-5.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-queryparser/7.2.1/lucene-queryparser-7.2.1.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/lucene/lucene-sandbox/7.2.1/lucene-sandbox-7.2.1.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/protobuf/protobuf-java/3.11.4/protobuf-java-3.11.4.jar:
/Users/lihuan/Documents/opt/maven/repository/com/googlecode/protobuf-java-format/protobuf-java-format/1.4/protobuf-java-format-1.4.jar:
/Users/lihuan/Documents/opt/maven/repository/org/ansj/ansj_seg/5.1.6/ansj_seg-5.1.6.jar:
/Users/lihuan/Documents/opt/maven/repository/org/nlpcn/nlp-lang/1.7.7/nlp-lang-1.7.7.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/guava/guava/27.1-jre/guava-27.1-jre.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar:/Users/lihuan/Documents/opt/maven/repository/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar:
/Users/lihuan/Documents/opt/maven/repository/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar:
/Users/lihuan/Documents/opt/maven/repository/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar:
/Users/lihuan/Documents/opt/maven/repository/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter-thymeleaf/2.3.2.RELEASE/spring-boot-starter-thymeleaf-2.3.2.RELEASE.jar:/Users/lihuan/Documents/opt/maven/repository/org/thymeleaf/thymeleaf-spring5/3.0.11.RELEASE/thymeleaf-spring5-3.0.11.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/thymeleaf/thymeleaf/3.0.11.RELEASE/thymeleaf-3.0.11.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/attoparser/attoparser/2.0.5.RELEASE/attoparser-2.0.5.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/unbescape/unbescape/1.1.6.RELEASE/unbescape-1.1.6.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar:/Users/lihuan/Documents/opt/maven/repository/org/thymeleaf/extras/thymeleaf-extras-java8time/3.0.4.RELEASE/thymeleaf-extras-java8time-3.0.4.RELEASE.jar:/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter-aop/2.3.2.RELEASE/spring-boot-starter-aop-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-aop/5.2.8.RELEASE/spring-aop-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/aspectj/aspectjweaver/1.9.6/aspectjweaver-1.9.6.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-context-support/4.3.13.RELEASE/spring-context-support-4.3.13.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-beans/5.2.8.RELEASE/spring-beans-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-context/5.2.8.RELEASE/spring-context-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/spring-expression/5.2.8.RELEASE/spring-expression-5.2.8.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/org/springframework/boot/spring-boot-starter-cache/2.3.2.RELEASE/spring-boot-starter-cache-2.3.2.RELEASE.jar:
/Users/lihuan/Documents/opt/maven/repository/com/github/ben-manes/caffeine/caffeine/2.5.5/caffeine-2.5.5.jar:/Users/lihuan/Documents/opt/maven/repository/com/github/rholder/guava-retrying/2.0.0/guava-retrying-2.0.0.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/httpcomponents/httpclient/4.5.10/httpclient-4.5.10.jar:
/Users/lihuan/Documents/opt/maven/repository/org/apache/httpcomponents/httpcore/4.4.13/httpcore-4.4.13.jar:
sun.misc.Launcher$AppClassLoader@18b4aac2

自定义类加载器

自定义类加载器都是ClassLoader的直接子类或者间接子类,java.lang.ClassLoader是一个抽象类,它里边并没有抽象方法,但是有一个findClass方法,务必实现该方法,否则会抛出Class找不到的异常。

package com.spring.master.java.loader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 自定义类加载器必须是ClassLoader的子类或者直接子类
 */
public class MyClassLoader extends ClassLoader{

    /**
     * 定义默认的class存放路径
     */
    private final static Path  DEFAULT_CLASS_DIR = Paths.get("/Users/lihuan/Downloads/classloader1");

    private final Path classDir;

    /**
     * 使用默认的class路径
     */
    public MyClassLoader() {
        super();
        this.classDir = DEFAULT_CLASS_DIR;
    }

    /**
     * 允许传入指定路径的class路径
     * @param classDir
     */
    public MyClassLoader(String classDir) {
        super();
        this.classDir = Paths.get(classDir);
    }

    /**
     * 指定class路径,同时指定父类加载器
     * @param classDir
     * @param parent
     */
    public MyClassLoader(String classDir, ClassLoader parent) {
        super(parent);
        this.classDir = Paths.get(classDir);
    }

    /**
     * 重写父类的findClass方法,这是至关重要的一步
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        // 读取class的二进制数据
        byte[] classBytes = this.readClassBytes(name);
        // 如果数据为null或者没有读到任何信息,则抛出ClassNotFoundException
        if (null == classBytes || classBytes.length == 0) {
            throw new ClassNotFoundException("Can not load the class" + name);
        }
        // 调用defineClass方法定义class
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    /**
     * 将class文件读入内存
     * @param name
     * @return
     */
    private byte[] readClassBytes(String name) throws ClassNotFoundException{

        String classPath = name.replace(".", "/");
        Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));

        if (!classFullPath.toFile().exists()) {
            throw new ClassNotFoundException("The class" + name + "not found");
        }

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Files.copy(classFullPath, baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new ClassNotFoundException("load the class" + name + "occur error", e);
        }
    }

    @Override
    public String toString() {
        return "My ClassLoader";
    }
}
全路径格式:
java.lang.String:包名.类名
javax.swing.JSpinnerDefaultEditor:包名.类名DefaultEditor:包名.类名内部类
java.security.KeyStoreBuilderBuilderFileBuilder1:包名.类名1:包名.类名内部类内部类内部类匿名内部类
java.net.URLClassLoader331:包名.类名匿名内部类匿名内部类匿名内部类

需要注意的是defineClass方法,该方法的完整方法描述是defineClass(String name, byte[] b, int off, int len),其中,第一个是要定义类的名字,一般与findClass方法中的类名保持一致即可;第二个参数是class文件的二进制字节数组;第三个参数是字节数组的偏移量;第四个参数从偏移量开始读取多长的byte数据。

双亲委托模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器

测试类加载器:

测试类:

public class HelloWord {

    static {
        System.out.println("Hello World Class is Init");
    }

    public String welcome() {
        return "Hello World";
    }
}
public class MyClassLoaderTest {

    public static void main(String[] args)
            throws ClassNotFoundException,
            IllegalAccessException,
            InstantiationException,
            NoSuchMethodException,
            InvocationTargetException {

        String path = "com.spring.master.java.loader.HelloWord";

        // 声明一个MyClassLoader
        MyClassLoader classLoader = new MyClassLoader();

        Class<?> aClass = classLoader.loadClass(path);

        System.out.println(aClass.getClassLoader()); #注释1

        Object object = aClass.newInstance();
        System.out.println(object);

        Method method = aClass.getMethod("welcome");
        System.out.println(method);

        String result = (String)method.invoke(object);
        System.out.println(result);
    }

}

输出:
sun.misc.Launcher$AppClassLoader@18b4aac2
Hello World Class is Init
com.spring.master.java.loader.HelloWord@3d494fbf
public java.lang.String com.spring.master.java.loader.HelloWord.welcome()
Hello World

在测试代码注释掉"注释1"以下的代码你会发现,虽然aClass被成功加载并且输出了类加载器的信息,但是HelloWorld的静态代码块并未得到输出,那是因为使用类加载器loadClass并不会导致类的主动初始化,它只是执行了加载过程中的加载阶段而已。

双亲委派机制

站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器,这个类加载起使用C++语言实现,是虚拟机自身的一部分;另一种是其他所有的类加载器,这些类加载器都由Java实现,独立存在于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader双亲委派机制的工作过程:如果一个类加载器收到类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派为自己父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

类加载器的父委托机制
类加载器的父委托机制

当前类在没有父类加载器的情况下,会直接使用启动类加载器对该类进行加载

破坏双清委托机制

JDK提供的双亲委托机制并非一个强制的模型,程序开发人员是可以对其灵活发挥破坏这种委托机制的,比如我们想要在程序运行时进行某个模块功能的升级,甚至是在不停止服务的前提下增加新的功能,这就是我们常说的热部署。热部署首先要卸载掉加载该模块的所有Class的类加载器,卸载类加载器会导致所有类的卸载,很显然我们无法对JVM三大内置加载器进行卸载,我们只能通过控制自定义类加载器才能做到这一点。

类加载命名空间

每一个类加载器实例都有各自的命名空间,命名空间是由该加载器极其所有的父加载器所构成的,因此在每个类加载器中同一个class都是独一无二的。

public class NameSpace {

    public static void main(String[] args) throws ClassNotFoundException{

        // 获取系统类加载器
        ClassLoader classLoader = NameSpace.class.getClassLoader();
        Class<?> aClass = classLoader.loadClass("com.spring.master.java.loader.NameSpace");
        Class<?> bClass = classLoader.loadClass("com.spring.master.java.loader.NameSpace");

        System.out.println(aClass.hashCode());
        System.out.println(bClass.hashCode());
        System.out.println(aClass == bClass);
    }
}

输出:
1338668845
1338668845
true

使用不同的类加载器,或者同一个类加载器的不同实例,去加载同一个class,则会在堆内存和方法区产生多个class的对象。

在类加载器进行加载的时候,首选会到加载记录表也就是缓存中,查看该类是否已经被加载过了,如果已经被加载过了,就不会重复加载,否则将会认为其实首次加载。

同一个class实例在JVM中存在一份这样的说法是不够的严谨的,跟准确的说应该是同一个class实例在同一个类加载器命名空间之下是唯一的

运行时包

我们在编写代码的时候通常会给一个类指定一个包名,包的作用是为了组织类,防止不同包下同样名称的class引起冲突,还能起到封装的作用,包名和类名构成了类的全限定名称。在JVM运行时class会有一个运行时包,运行时包是由类加载器的命名空间和类的全限定名称共同组成的。

比如MyClassLoaderTest的运行时包:
BootStrapClassLoader.ExtClassLoader.ApplicationClassLoader.MyClassLoader.com.spring.master.java.loader.MyClassLoaderTest

初始类加载器

由于运行时包的存在,JVM规定了不同的运行时包下的类彼此之间是不可以进行访问的,那么问题来了,为什么我们在开发的程序中可以访问java.lang包下的类呢?

每个类在经过ClassLoader的加载之后,在虚拟机中都会有对应Class实例,如果某个类C被加载器CL加载,那么CL就被称为C的初始类加载器。JVM为每个类加载器维护了一个列表,该列表中记录了将该类加载器作为初始类加载器的所有class,在加载一个类时,JVM使用这些列表来判断该类是否已经被加载过了是否需要首次加载。

根据JVM规范的规定,在类的加载过程中,所有参与的类加载器,即使没有亲自加载过该类,也都会被标识为该类的初始类加载器,比如java.lang.String首先经过了BrokerDelegateClassLoader类加载器,依次又经过系统类加载器,扩展类加载器,启动类加载器,这些类加载器都是java.lang.String的初始类夹杂器,JVM会在每个类加载器维护的列表中添加该class类型。

类的卸载

在JVM的启动过程中,JVM会加载很多的类,在运行期间也会加载很多类,比如自定义的类加载器进行类的加载。

关系JVM在运行期间到底加载了多少的class,可以在启动JVM时指定-verbose:class参数观察得到,我们知道某个对象在堆内存中如果没有其他地方的引用则会在垃圾回收器线程进行GC的时候被回收掉,那么该对象在堆内存中的Class对象以及Class方法区中的数据结构如何被回收呢?

JVM规定了一个Class只有在满足以下三个条件的时候才会被GC回收,也就是类被卸载。

  • 该类所有的实例都已经被GC,比如Simple.class的所有Simple实例都被回收掉
  • 加载该类的ClassLoader实例被回收
  • 该类的class实例没有再其他地方被引用

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类加载器
    • 启动类加载器
      • 扩展类加载器
        • 系统类加载器
          • 自定义类加载器
          • 双亲委派机制
            • 破坏双清委托机制
              • 类加载命名空间
                • 运行时包
                  • 初始类加载器
                    • 类的卸载
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档