类加载
类加载指的是将类的class文件的二进制数据加载到内存当中,并将其放在运行时数据区的方法区的内,然后在内存中创建一个java.lang.Class对象,用来封装类在方法区中的数据结构。
Java虚拟机规范并未要求将Class对象位于哪里,HotSpot虚拟机将之放在了方法区内
Java代码中,类型的加载、连接、初始化过程都是在运行期间完成的 其中,连接又分为验证、准备和解析3个阶段
提供更大的灵活性,增加更大的可能性。
类从加载进入内存到从内存中卸载,一共经过以几个阶段:
- 加载(Loading)
查找并加载类的二进制数据
- 连接(Connection)
- 验证 :校验class文件的合法性
- 准备 :为类的静态变量分配内存并初始化为0值,即对应类型的默认值
- 解析 :将类的符号引用转换为直接引用
- 初始化(Initialization)
为类的静态变量赋予正确的初始值
举例:
准备阶段,a = 0; 初始化阶段,a = 1;
public class Test{public static int a = 1;}
- 使用(Using)
- 卸载Unloading)
JVM与程序生命周期
程序遇到以下情况会退出虚拟机,结束进程:
- 显示调用 System.exit()
- 程序正常结束
- 程序在运行期间遇到错误导致异常终止
- 操作系统发生错误导致进程退出
类的主动使用
所有Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化
**
- 创建类的实例
- 访问某个类或接口的静态变量,或对该静态变量进行赋值
putstaticgetstatic
- 调用类的静态方法
invokestatic
- 反射
- 初始化一个类的子类
- 当虚拟机启动时,用户需要指定一个执行的主类(main()方法所在类),虚拟机会先触发其初始化
- 使用 JDK 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getstatic、REF_putstatic、REF_invikestatic 的方法句柄时,并且这个句柄对应的类还未初始化时,必须先要触发其初始化
代码验证
class Parent {public static String con = "parent";static {System.out.println("parent静态代码块");}{System.out.println("parent代码块");}}class Child extends Parent {{System.out.println("child代码块");}static {System.out.println("child静态代码块");}public static void invoke() {System.out.println("child 静态方法");}}
- 验证
创建类的实例:
public static void main(String[] args) {new Parent();}

可以看到,new Parent() 导致了 Parent 类的初始化。
- 验证
访问某个类或接口的静态变量,或对该静态变量进行赋值
public static void main(String[] args) {System.out.println(Parent.con);}

- 验证
调用类的静态方法 - 验证
初始化一个类的子类
public static void main(String[] args) {Child.invoke();}

可以看出,调用子类的静态方法会导致其初始化,也会导致父类的初始化。
- 验证
反射
public static void main(String[] args) throws ClassNotFoundException {Class<?> aClass = Class.forName("com.oliver.jvm.Parent");}

- 验证
当虚拟机启动时,用户需要指定一个执行的主类(main()方法所在类),虚拟机会先触发其初始化
public static void main(String[] args) throws ClassNotFoundException {}static {System.out.println("main静态代码块");}

除了以上的7种方式,其余的都不会导致类的主动使用。例如:
public static void main(String[] args) throws ClassNotFoundException {System.out.println(Child.con);}

虽然调用了Child.con,但是由于 con是属于 Parent类的,因此并不会导致Child的主动使用。虽然Child类并没有初始化,但是有没有被加载呢?这在虚拟机规范中并没有明确说明。即使这样,我们也可以通过一些配置项来查看类的加载信息。
-XX:+TraceClassLoading

再次运行代码:
23:34:03: Executing task 'Demo01.main()'...> Task :compileJava UP-TO-DATE> Task :processResources NO-SOURCE> Task :classes UP-TO-DATE> Task :Demo01.main()[0.004s][warning][arguments] -XX:+TraceClassLoading is deprecated. Will use -Xlog:class+load=info instead.[0.004s][warning][arguments] -XX:+TraceClassLoading is deprecated. Will use -Xlog:class+load=info instead.[0.011s][info ][class,load] opened: E:\workspace\IDEA\IntelliJ IDEA Community Edition 2019.2\jbr\lib\modules[0.018s][info ][class,load] java.lang.Object source: jrt:/java.base......[0.134s][info ][class,load] com.oliver.jvm.Demo01 source: file:/L:/jvm/jvm/build/classes/java/main/[0.134s][info ][class,load] java.lang.PublicMethods$MethodList source: jrt:/java.base[0.134s][info ][class,load] java.lang.PublicMethods$Key source: jrt:/java.base[0.134s][info ][class,load] java.lang.Void source: jrt:/java.base[0.135s][info ][class,load] com.oliver.jvm.Parent source: file:/L:/jvm/jvm/build/classes/java/main/[0.136s][info ][class,load] com.oliver.jvm.Child source: file:/L:/jvm/jvm/build/classes/java/main/parent��̬�����parent[0.136s][info ][class,load] jdk.internal.misc.TerminatingThreadLocal$1 source: jrt:/java.base[0.136s][info ][class,load] java.lang.Shutdown source: jrt:/java.base[0.136s][info ][class,load] java.lang.Shutdown$Lock source: jrt:/java.baseDeprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.Use '--warning-mode all' to show the individual deprecation warnings.See https://docs.gradle.org/5.2.1/userguide/command_line_interface.html#sec:command_line_warningsBUILD SUCCESSFUL in 0s

final关键字
class Parent {public static final String con = "parent";static {System.out.println("parent静态代码块");}}
public static void main(String[] args) throws ClassNotFoundException {System.out.println(Parent.con);}

可以看到,con 加了 final 关键字之后,该类不会被主动加载了。为什么呢?因为在常量编译期间,该常量会存入到调用这个常量的方法所在的常量池中。也就是说,常量 con 已经在编译期间添加到了main() 方法所在的方法区内。具体反编译class文件看看究竟。
// 反编译class文件javap -c xxx.classCompiled from "Demo01.java"public class com.oliver.jvm.Demo01 {public com.oliver.jvm.Demo01();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]) throws java.lang.ClassNotFoundException;Code:0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #4 // String parent5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: return}
可以看到,在第15行有条虚拟机执行指令:ldc.这条指令直接指向了 parent字符串,可以看出该字符串在此处已经与 Parent类没有关系了。
ldc:将
int,float或String型常量值从常量池中推送至栈顶
更多虚拟机指令参照 JVM系列—指令集
**
当去掉final关键字之后反编译class文件:
Compiled from "Demo01.java"public class com.oliver.jvm.Demo01 {public com.oliver.jvm.Demo01();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]) throws java.lang.ClassNotFoundException;Code:0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: getstatic #3 // Field com/oliver/jvm/Parent.con:Ljava/lang/String;6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V9: return}
getstatic 获取指定类的静态域, 并将其压入栈顶
从12行可以看出,parant这个字符串来自于Parent类。
JVM 配置
JVM的配置项都遵循一定的规律。
-XX:+<options> 开启options选项-XX:-<options> 关闭options选项-XX:<options>=<values> 将options的值设置成为values
例如 -XX:MaxNewSize=size **新生成对象能占用内存的最大值 -XX:-HeapDumpOnOutOfMemoryError **当首次遭遇OOM时导出此时堆中相关信息【关闭】
