专栏导航
目录
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了类的生命周期、类的初始化阶段等内容。
类的生命周期描述了一个类加载、连接、初始化、使用、卸载的整个过程。
加载阶段是类的生命周期的起始点。当应用程序首次需要使用某个类时,Java虚拟机(JVM)会负责加载这个类。加载是通过类的加载器(ClassLoader)完成的,它会查找并加载类的二进制数据。这个过程包括将类的字节码从文件系统、JAR文件或网络加载到内存中。
连接阶段是加载阶段的后续,它包括验证、准备和解析三个子阶段。
初始化阶段是类加载过程中的最后一步,当准备和解析阶段完成后,JVM会执行类的构造器方法,这个方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块集合来的。需要注意的是,构造器方法中的代码只在类被首次使用时执行一次。
一旦类被成功加载、连接并初始化后,就可以被实例化并用于执行应用程序的业务逻辑。在应用程序运行期间,类可能会被频繁地使用。
当应用程序不再需要某个类时,该类的实例以及与其相关的资源将会被回收,这个过程就是卸载。但是需要注意的是,只有当一个类不再被任何活动对象所引用时,它才会被卸载。另外,JVM的垃圾回收机制(Garbage Collection, GC)负责自动处理类的卸载和资源的回收。
初始化阶段是类生命周期中的关键阶段之一,主要涉及静态代码块的执行和静态变量的初始化。这个阶段是在类首次被加载到内存中或者在首次实例化这个类的对象时发生的。
在初始化阶段,会执行静态代码块中的代码,这些代码块在类定义中以static声明的部分。这些代码块仅在类首次被加载时执行一次,主要用于执行一些仅需在类加载时进行的初始化操作,如静态变量的赋值等。
此外,初始化阶段会执行字节码文件中clinit部分的字节码指令。clinit方法是Java编译器自动生成的特殊方法,用于执行所有类级别的初始化操作,包括对静态变量的赋值、静态代码块的执行等。clinit方法的执行顺序与Java源代码中编写的顺序一致,确保了按照源代码的顺序进行初始化。
案例:
public class Demo1 {
public static int value = 1;
static {
value = 2;
}
public static void main(String[] args) {
}
}
字节码信息:
<init> | 构造方法 |
main | Main方法 |
<clinit> | 初始化阶段执行 |
clinit部分的字节码指令:
0 iconst_1
1 putstatic #2 <init/Demo1.value : I>
4 iconst_2
5 putstatic #2 <init/Demo1.value : I>
8 return
指令解析:
iconst_1 | 将常量1放入操作数栈 |
putstatic #2 <init/Demo1.value : I> | 从操作数栈中获取值设置到静态变量中 |
初始化步骤:
类的初始化可以通过以下几种方式触发:
案例:
public class Demo1 {
public static void main(String[] args) {
int i = Test.i;
System.out.println(i);
}
}
class Test{
static {
System.out.println("初始化");
}
public static int i = 0;
}
添加-XX:+TraceClassLoading 参数可以打印出加载并初始化的类。
-XX:+TraceClassLoading
运行结果:
Class.forName(String className) 源码:
@CallerSensitive
public static Class<?> forName(String var0) throws ClassNotFoundException {
Class var1 = Reflection.getCallerClass();
return forName0(var0, true, ClassLoader.getClassLoader(var1), var1);
}
@CallerSensitive
public static Class<?> forName(String var0, boolean var1, ClassLoader var2) throws ClassNotFoundException {
Class var3 = null;
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var3 = Reflection.getCallerClass();
if (VM.isSystemDomainLoader(var2)) {
ClassLoader var5 = ClassLoader.getClassLoader(var3);
if (!VM.isSystemDomainLoader(var5)) {
var4.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(var0, var1, var2, var3);
}
private static native Class<?> forName0(String var0, boolean var1, ClassLoader var2, Class<?> var3) throws ClassNotFoundException;
boolean var1参数表示是否初始化这个类:
案例:?
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> demo2 = Class.forName("Test.Demo2");
}
}
class Demo2 {
static {
System.out.println("初始化");
}
}
运行结果:
案例:?
public class Demo3 {
static {
System.out.println("Demo3初始化");
}
public static void main(String[] args) throws ClassNotFoundException {
new Demo4();
}
}
class Demo4{
static {
System.out.println("Demo4初始化");
}
}
运行结果:
在字节码层面,初始化阶段是通过执行clinit方法来完成的。然而,值得注意的是,clinit方法并不是在所有情况下都会出现。以下是一些特定情况下不会执行clinit方法的情况:
案例:?
public class Demo1 {
public static void main(String[] args) {
}
}
?字节码信息:
案例:?
public class Demo1 {
public static int i;
public static void main(String[] args) {
}
}
??字节码信息:
案例:?
public class Demo1 {
public static final int i = 1;
public static void main(String[] args) {
}
}
??字节码信息:
在继承关系下,类的生命周期中的初始化阶段变得更为复杂。子类和父类之间的初始化交互是理解这个过程的关键。
JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了类的生命周期、类的初始化阶段等内容,希望对大家有所帮助。