Java类的加载

本文详细介绍了Java类的加载过程,包括加载阶段的三个步骤,以及Class对象的含义和作用。重点讲解了ClassLoader的父加载器概念,特别是双亲委托机制,涉及Bootstrap ClassLoader、Extension ClassLoader和Application ClassLoader。文中还提到了用户自定义加载器以及类加载器的层次关系,强调了类加载的动态性和双亲委托模型的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类加载

我们知道,Java文件被编译成 .class 文件,然后被加载到JVM内存中(如果对JVM内存了解,就会发现是方法区)。之后进行验证、准备、解析、初始化,然后就可以被使用了。当然,不在需要的时候,也可以将类从内存中卸载。

其实在加载阶段,JVM需要完成以下三件事情:
1. 通过一个类的全限定名来获取其定义的二进制字节流。(一般是根据类名)
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3. 在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口。

此时类的Class数据就在方法区中了,且每个类只有一份。另外唯一一个该类的Class对象在堆中

public class HelloWold {
    public static void main(String[] args) {
        System.out.println(HelloWold.class instanceof Class);// true

        System.out.println(HelloWold.class.hashCode());// 1
        HelloWold hw = new HelloWold();
        HelloWold hw2 = new HelloWold();
        System.out.println(hw.getClass().hashCode());// 2
        System.out.println(hw2.getClass().hashCode());// 3
    }
}
运行代码,就会发现1,2,3 三个地方输出的值是一样的。

Class

Class类其实也是一个类,只不过比较特殊。特殊在这是一个在类加载过程中由虚拟机生成的,表示被加载类的类型信息的对象。
比如:HelloWold hw = new HelloWold();怎么知道hw 的类型是HelloWold呢?就是通过这个Class类知道的。

而且Class里面存储了对应类的几乎所有的信息,当然这些信息是未初始化的信息,比如所有的方法,所有的构造函数,所有的字段(成员属性)等等。Java 反射的前提就是获取Class对象。

打开源码就会发现,Class 类只有私有构造,而且是final的。

    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

而真正构造的地方在 ClassLoader 的defineClass 方法中,该方法是native的:

    private native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd);

    private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

    private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
                                         int off, int len, ProtectionDomain pd,
                                         String source);


ClassLoader

最近使用PowerMock做单元测试,发现它用的都是自己的ClassLoader。这里整理下Java 类的加载机制。

简单看两个测试,发现它们是被不同的类加载加载的

public class ParkingTest {
    @Test
    public void should_print_classloadName() {
        System.out.println(this.getClass().getClassLoader().getClass().getName());
        // sun.misc.Launcher$AppClassLoader
        
        System.out.println((new NoSuchDynamicMethodException("java.ext.dirs")).getClass().getClassLoader().getClass().getName());
        // sun.misc.Launcher$ExtClassLoader

        System.out.println(System.getProperty("java.ext.dirs"));
        // C:\Java\jdk1.8.0_66\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
    }
}
@RunWith(PowerMockRunner.class)
public class MockSupperTest {
    // ...
    @Test
    public void should_print_classloadName() {
        // ...
        System.out.println(this.getClass().getClassLoader().getClass().getName());
        // org.powermock.core.classloader.MockClassLoader
    }
}

从输出发现:

1. ParkingTest 由AppClassLoader 加载器加载
2. ExtClassLoader 加载器加载"java.ext.dirs"指向的类库。

3. MockClassLoader 是PowerMock 库自定义的类加载器。

其实加上JVM自带的BootstrapClassLoader(启动类加载器),上边代码一共用到四类加载器:

启动类加载器:Bootstrap ClassLoader。它负责加载存"sun.boot.class.path"指定的类,或被-Xbootclasspath参数指定的路径中的类(如 rt.jar,所有的java.*开头的类均被 Bootstrap ClassLoader 加载)。注意:启动类加载器是无法被 Java 程序直接引用的。
扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
应用程序类加载器:Application ClassLoader,该类加载器由 sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

用户自定义加载器:用户实现的类加载器,必须是ClassLoader 的子类。

类图结构如下:


sun.misc.Launcher.class 中代码如下:

public class Launcher {
	static class AppClassLoader extends URLClassLoader {}
	static class ExtClassLoader extends URLClassLoader {}
}

父加载器

不同于上边的继承关系,这四类加载器是有层次关系的。

这种层次关系称为类加载器的双亲委派模型。每一层上面的类加载器叫做当前层类加载器的父加载器。
注意:它们之间的父子关系并不是通过继承关系来实现的,而是使用组合关系来复用父加载器中的代码。

Java程序启动时,并不是一次全部加载所有的类。首先把保证程序运行的基础类一次性加载到jvm中,其它类要等到jvm用到的时候才加载。
1.虚拟机一启动,会先做一些初始化的动作,此时创建Bootstrp Loader。
2.Bootstrp Loader初始化时,会加载并定义Launcher$ExtClassLoader.class,同时建立父子类加载器关系。这就有了ExtClassLoader。

3.Bootstrap Loader ,再要求加载定义Launcher$AppClassLoader.class,并将它设为ExtClassLoader 的子类加载器。AppClassLoader是Java程序默认的类加载器。


双亲委托机制

ClassLoader.getSystemClassLoader()方法,此方法返回的正是AppclassLoader.

委托模型机制,简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”。

从上边可以知道几件事:
1. AppClassLoader是Java程序默认的类加载器。
2. 在图中,下层ClassLoader 是委托上层ClassLoader 实现关系的,因此下层ClassLoader 知道上层ClassLoader已经加载的东西。反过来不行。
3. 每次加载类时,会先查找,查找时也是每层查找。这样保证每个类都只会被加载一次(自己强制设置加载例外)。


类加载器BootstrapExtClassLoaderAppClassLoader
实现C++sun.misc.Launcher$ExtClassLoadersun.misc.Launcher$AppClassLoader
获取方式Java获取不到ClassLoader.getSystemClassLoader().getParent()ClassLoader.getSystemClassLoader()
System Property"sun.boot.class.path""java.ext.dirs""java.class.path"
所加载的类JDK\jre\lib\resources.jar
JDK\jre\lib\rt.jar
JDK\jre\lib\sunrsasign.jar
JDK\jre\lib\jsse.jar
JDK\jre\lib\jce.jar
JDK\jre\lib\charsets.jar
JDK\jre\lib\jfr.jar
JDK\jre\classes
JDK\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
应用程序类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值