类的主动使用和被动使用
1. 主动使用(触发初始化)
定义 :直接引用类的方式会导致 JVM 必须初始化该类 (执行 <clinit>
方法)。
场景:
操作 | 示例 | 说明 |
---|---|---|
创建类的实例 | new MyClass() |
触发初始化 |
访问类的静态变量(非 final) | MyClass.staticField |
非编译期常量需初始化 |
调用类的静态方法 | MyClass.staticMethod() |
直接触发初始化 |
反射调用(如 Class.forName ) |
Class.forName("MyClass") |
默认初始化(除非指定不初始化) |
初始化子类 | class Child extends Parent |
父类需先初始化 |
作为程序入口的主类 | public static void main(String[]) |
JVM 首先初始化主类 |
2. 被动使用(不触发初始化)
定义 :引用类的方式 不会导致类的初始化 (但可能触发加载和链接阶段)。
场景:
操作 | 示例 | 说明 |
---|---|---|
访问编译期常量(static final) | MyClass.CONSTANT |
常量在编译期存入调用类的常量池 |
通过数组定义类 | MyClass[] arr = new MyClass[10] |
数组类型由 JVM 动态生成 |
引用类的静态字段(非主动访问) | System.out (PrintStream 类) |
仅加载但不初始化 PrintStream |
类加载器的 loadClass 方法 |
ClassLoader.loadClass("MyClass") |
默认不初始化 |
还记不记得主包第二篇讲类的加载的时候说过,不是所有的类都会到第三阶段,也就是初始化阶段,只会经历前两个阶段也就是加载、链接。这里面就涉及到主动与被动使用的关系。忘记的小伙伴可以回头看看,这里提这个呢只是把这个点重新强调一下。
类的卸载与元空间GC
前面说了类的加载,那么肯定就有类的卸载以及元空间的GC,那么元空间的GC又是什么呢?通常情况下元空间是很难发生GC的,一般都是跟随FULLGC一起执行GC,但也有需要GC的时候,这个时候呢就会把要卸载的类和类加载器进行回收,每一个回收的类元数据会被合并成一整块空闲内存进行下次类的加载使用。
1. 类的卸载(Class Unloading)
类的卸载必须同时满足以下条件:
- 类的所有实例已被回收:堆中不存在该类的任何实例。
- 类的
Class
对象不可达 :没有地方通过反射或其他方式持有该类的Class
对象。 - 加载该类的
ClassLoader
已被回收:类加载器本身被卸载。 - 类未被主动引用:无代码、线程栈、静态变量等引用该类。
2. 什么条件触发 GC?
GC 的触发与类的卸载密切相关,主要场景如下:
(1) 堆内存不足
- Young GC :Eden 区满时触发(
Minor GC
)。 - Full GC :老年代或元空间不足时触发(
Major GC
)。
(2) 元空间不足
Metaspace GC :当元空间使用量超过 MetaspaceSize
或 MaxMetaspaceSize
时触发。
自定义类加载器
我们之前也有说过可以自定义类加载器,作用呢其实也是蛮多的比如热更新、加载网络或者数据库、加密后的字节码文件、破双亲委派机制等等。那具体怎么去实现呢?首先我们需要继承
ClassLoader这个类。
java
public class MyClassLoader extends ClassLoader {
// 需要重写关键方法
}
然后重写findClass这个方法,通常就是这个方法,然后像破除双亲委派就可以重写loadClass方法,因为loadClass方法会调用findClass方法的,但是不建议这么做除非有业务需求。
java
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
这个就是findClass的源码,具体怎么实现这里就不教了,要根据自己的业务进行编写业务代码的,然后就是在一个监听器或者一个要使用的接口中new这个对象就可以了。

这个是部分的源码,有兴趣的小伙伴自己去idea查看吧,这边就不做多的解释了。
总结
这篇主要是对基础篇的一些补充,下一篇开始真正的JVM调优阶段。