类的生命周期
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载 (Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称 为连接(Linking)。
详情可参考我的另一篇文章JVM (八)大厂必问类加载机制,加载过程,生命周期
这里重点代码举例初始化过程
2.主动引用和被动引用
关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行 强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》 则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之 前开始):
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始 化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
1)使用new关键字实例化对象的时候。
2)读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外) 的时候。
3)调用一个类型的静态方法的时候。 - 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需 要先触发其初始化。
- 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先 初始化这个主类。
- 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解 析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句 柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
- 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有 这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
主动引用之外,所有引用类型的方 式都不会触发初始化,称为被动引用。
其中第二条就是本文重点,反射class.forName为主动引用,而通过classloader使用则为被动引用
代码举例
我们使用代码,先看看如何使用。注意包的范围,避免加载不了。第一步:定义ClassDemo类
public class ClassDemo {
private static int a = 1;
private static int b = inttB();
{
System.out.println("这是普通代码块");
}
static{
System.out.println("这是静态代码块");
}
private static int inttB() {
System.out.println("静态成员变量初始化时调通了静态方法");
return 6;
}
}
测试
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException {
String classDemo = "ClassDemo";
ClassLoader loader = ClassLoader.getSystemClassLoader();
System.out.println("classLoader test ------------------------------------");
Class<?> aClass = loader.loadClass(classDemo);
System.out.println(aClass.getName());
System.out.println("我是一条分割线--------------------------------");
Class<?> aClass1 = Class.forName(classDemo);
System.out.println("Class.forName test------------------------------------");
System.out.println(aClass1.getName());
}
}
结果
从结果看到通过classloader为被动引用,并没有引发类的初始化过程,而通过反射则进行了初始化
初始化会执行clinit()方法,该方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的 语句合并产生的
clinit()方法与类的构造函数(即在虚拟机视角中的实例构造器init()方法)不同,它不需要显 式地调用父类构造器。
源码分析
class.forName
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflective call to get caller class is only needed if a security manager
// is present. Avoid the overhead of making this call otherwise.
caller = Reflection.getCallerClass();
if (sun.misc.VM.isSystemDomainLoader(loader)) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}
在这个源码中我们会发现,其实底层真正实现的是forName0方法,那这几个参数又是什么意思呢?
(1)className:表示我们要加载的类名
(2)true:指Class被加载后是不是必须被初始化。 不初始化就是不执行static的代码即静态代码,在这里默认为true,也就是默认实现类的初始化。
(3)ClassLoader.getClassLoader(caller):表示类加载器,到这你会发现forNanme其实也是使用的ClassLoader类加载器加载的。
(4)caller:指定类加载器。所以,在这里你可以指定是否在class加载后被初始化。而且底层还是使用的classloader加载的。
2、classloader
在上面的案例中我们发现,classloader并没有初始化静态块,原因最好还是到源码中看。首先我们先进入到loadclass方法中的源码。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
这一步看起来还看不明白,没关系这里真正实现的是内部的loadclass,我们再跟进去看看。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
这个才是真正实现的方法,在这里的步骤其实很简单,大致流程是先判断class是否已经被加载,如果被加载了那就重新加载,如果没有加载那就使用双亲委派原则加载。加载的时候并没有指定是否要进行初始化。
总结
通过classloader为类型的被动引用,并没有引发类的初始化过程,而通过反射则是主动引用会进行类的初始化过程。