类加载

类加载指的是将类的class文件的二进制数据加载到内存当中,并将其放在运行时数据区的方法区的内,然后在内存中创建一个java.lang.Class对象,用来封装类在方法区中的数据结构。

Java虚拟机规范并未要求将Class对象位于哪里,HotSpot虚拟机将之放在了方法区内

Java代码中,类型的加载、连接、初始化过程都是在运行期间完成的 其中,连接又分为验证、准备和解析3个阶段

提供更大的灵活性,增加更大的可能性。

类从加载进入内存到从内存中卸载,一共经过以几个阶段:

  • 加载(Loading)

查找并加载类的二进制数据

  • 连接(Connection)
    1. 验证 :校验class文件的合法性
    2. 准备 :为类的静态变量分配内存并初始化为0值,即对应类型的默认值
    3. 解析 :将类的符号引用转换为直接引用
  • 初始化(Initialization)

为类的静态变量赋予正确的初始值

举例:

准备阶段,a = 0; 初始化阶段,a = 1;

  1. public class Test{
  2. public static int a = 1;
  3. }
  • 使用(Using)
  • 卸载Unloading)

JVM与程序生命周期

程序遇到以下情况会退出虚拟机,结束进程:

  1. 显示调用 System.exit()
  2. 程序正常结束
  3. 程序在运行期间遇到错误导致异常终止
  4. 操作系统发生错误导致进程退出

类的主动使用

所有Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化

**

  • 创建类的实例
  • 访问某个类或接口的静态变量,或对该静态变量进行赋值
  1. putstatic
  2. getstatic
  • 调用类的静态方法
  1. invokestatic
  • 反射
  • 初始化一个类的子类
  • 当虚拟机启动时,用户需要指定一个执行的主类(main()方法所在类),虚拟机会先触发其初始化
  • 使用 JDK 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getstatic、REF_putstatic、REF_invikestatic 的方法句柄时,并且这个句柄对应的类还未初始化时,必须先要触发其初始化

代码验证

  1. class Parent {
  2. public static String con = "parent";
  3. static {
  4. System.out.println("parent静态代码块");
  5. }
  6. {
  7. System.out.println("parent代码块");
  8. }
  9. }
  10. class Child extends Parent {
  11. {
  12. System.out.println("child代码块");
  13. }
  14. static {
  15. System.out.println("child静态代码块");
  16. }
  17. public static void invoke() {
  18. System.out.println("child 静态方法");
  19. }
  20. }
  1. 验证创建类的实例
  1. public static void main(String[] args) {
  2. new Parent();
  3. }

捕获.PNG

可以看到,new Parent() 导致了 Parent 类的初始化。

  1. 验证访问某个类或接口的静态变量,或对该静态变量进行赋值
  1. public static void main(String[] args) {
  2. System.out.println(Parent.con);
  3. }

捕获.PNG

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

捕获aaaa.PNG

可以看出,调用子类的静态方法会导致其初始化,也会导致父类的初始化。

  1. 验证反射
  1. public static void main(String[] args) throws ClassNotFoundException {
  2. Class<?> aClass = Class.forName("com.oliver.jvm.Parent");
  3. }

捕获sssss.PNG

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

捕获.PNG

除了以上的7种方式,其余的都不会导致类的主动使用。例如:

  1. public static void main(String[] args) throws ClassNotFoundException {
  2. System.out.println(Child.con);
  3. }

捕获.PNG

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

-XX:+TraceClassLoading

image.png

再次运行代码:

  1. 23:34:03: Executing task 'Demo01.main()'...
  2. > Task :compileJava UP-TO-DATE
  3. > Task :processResources NO-SOURCE
  4. > Task :classes UP-TO-DATE
  5. > Task :Demo01.main()
  6. [0.004s][warning][arguments] -XX:+TraceClassLoading is deprecated. Will use -Xlog:class+load=info instead.
  7. [0.004s][warning][arguments] -XX:+TraceClassLoading is deprecated. Will use -Xlog:class+load=info instead.
  8. [0.011s][info ][class,load] opened: E:\workspace\IDEA\IntelliJ IDEA Community Edition 2019.2\jbr\lib\modules
  9. [0.018s][info ][class,load] java.lang.Object source: jrt:/java.base
  10. ......
  11. [0.134s][info ][class,load] com.oliver.jvm.Demo01 source: file:/L:/jvm/jvm/build/classes/java/main/
  12. [0.134s][info ][class,load] java.lang.PublicMethods$MethodList source: jrt:/java.base
  13. [0.134s][info ][class,load] java.lang.PublicMethods$Key source: jrt:/java.base
  14. [0.134s][info ][class,load] java.lang.Void source: jrt:/java.base
  15. [0.135s][info ][class,load] com.oliver.jvm.Parent source: file:/L:/jvm/jvm/build/classes/java/main/
  16. [0.136s][info ][class,load] com.oliver.jvm.Child source: file:/L:/jvm/jvm/build/classes/java/main/
  17. parent��̬�����
  18. parent
  19. [0.136s][info ][class,load] jdk.internal.misc.TerminatingThreadLocal$1 source: jrt:/java.base
  20. [0.136s][info ][class,load] java.lang.Shutdown source: jrt:/java.base
  21. [0.136s][info ][class,load] java.lang.Shutdown$Lock source: jrt:/java.base
  22. Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
  23. Use '--warning-mode all' to show the individual deprecation warnings.
  24. See https://docs.gradle.org/5.2.1/userguide/command_line_interface.html#sec:command_line_warnings
  25. BUILD SUCCESSFUL in 0s

image.png

可以看出,Child类虽然没有初始化,但是仍然被加载了。

final关键字

  1. class Parent {
  2. public static final String con = "parent";
  3. static {
  4. System.out.println("parent静态代码块");
  5. }
  6. }
  1. public static void main(String[] args) throws ClassNotFoundException {
  2. System.out.println(Parent.con);
  3. }

image.png
可以看到,con 加了 final 关键字之后,该类不会被主动加载了。为什么呢?因为在常量编译期间,该常量会存入到调用这个常量的方法所在的常量池中。也就是说,常量 con 已经在编译期间添加到了main() 方法所在的方法区内。具体反编译class文件看看究竟。

  1. // 反编译class文件
  2. javap -c xxx.class
  3. Compiled from "Demo01.java"
  4. public class com.oliver.jvm.Demo01 {
  5. public com.oliver.jvm.Demo01();
  6. Code:
  7. 0: aload_0
  8. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  9. 4: return
  10. public static void main(java.lang.String[]) throws java.lang.ClassNotFoundException;
  11. Code:
  12. 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  13. 3: ldc #4 // String parent
  14. 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  15. 8: return
  16. }

可以看到,在第15行有条虚拟机执行指令:ldc.这条指令直接指向了 parent字符串,可以看出该字符串在此处已经与 Parent类没有关系了。

ldc:将int, floatString型常量值从常量池中推送至栈顶

更多虚拟机指令参照 JVM系列—指令集
**
当去掉final关键字之后反编译class文件:

  1. Compiled from "Demo01.java"
  2. public class com.oliver.jvm.Demo01 {
  3. public com.oliver.jvm.Demo01();
  4. Code:
  5. 0: aload_0
  6. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  7. 4: return
  8. public static void main(java.lang.String[]) throws java.lang.ClassNotFoundException;
  9. Code:
  10. 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  11. 3: getstatic #3 // Field com/oliver/jvm/Parent.con:Ljava/lang/String;
  12. 6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  13. 9: return
  14. }

getstatic 获取指定类的静态域, 并将其压入栈顶

从12行可以看出,parant这个字符串来自于Parent类。

JVM 配置

JVM的配置项都遵循一定的规律。

  1. -XX:+<options> 开启options选项
  2. -XX:-<options> 关闭options选项
  3. -XX:<options>=<values> options的值设置成为values

例如 -XX:MaxNewSize=size **新生成对象能占用内存的最大值 -XX:-HeapDumpOnOutOfMemoryError **当首次遭遇OOM时导出此时堆中相关信息【关闭】