JVM 之类加载

658 阅读7分钟

前言

JVM偏理论,请坚持。
一些相关工具有:jconsole,jvisualvm.exe,jmap,jps,jstack,,在控制台直接用即可。

JVM是?

  • 与其说Java是一个非常不错的语言,且设计的非常不错,不如说是JVM的平台设计的非常好。
  • 因为它不仅支持Java这样优秀的语言,它还支持其它语言,如Kotlin、Scala。。
  • 也就是说Java和JVM并不是一种紧密的绑定关系。
  • 也就是说在JVM上运行的并不是语言,而是class文件,即字节码文件,任何语言只要能翻译成JVM说能识别和运行的字节码,它就可以在JVM上运行。
  • HotSpot 是sun公司的JVM实现,还有一些商用的JVM的实现。
  • JVM是有规范的,只要根据JVM的规范你就能实现你自己的JVM。

类加载(ClassLoading)

在Java代码中,类型的加载、连接与初始化过程都是在程序运行期间完成的

  • 类型:是我们平时写代码时定义的class,interface,枚举等,称之为类型。并不是对象!!!

    • 类型的加载是在运行期间(Runtime)完成的。把运行期间的类型有机的装配在看一起。
    • Java是静态的语言,但却有动态语言的一些特性。
    • 因为它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。
    • 比如Java的反射机制,就可以体现其动态性。
  • 类的加载一般分三个阶段完成的 :类型的加载、连接与初始化。

  • 典型的类型加载,就是将磁盘中的class文件加载到内存中。(并不是唯一的方式)

  • 连接:有很多步骤,简单来说就是,确定类与类之间的关系,以及字节码的校验之类的,还有类与类之间的调用关系,比如符号引用转换为直接引用等,就在这个阶段完成的。

    你可能会说字节码有啥好校验的,只要Java代码没错,字节码怎么会错?一般情况下不会错,但我手动打开class文件,修改里面的内容,它就可能出错了,所以需要校验。

  • 初始化:就是给类中的静态变量赋值

并不是严格的按照以上三个步骤进行,有可能在类型的加载过程中进行连接操作,不同的JVM实现会有一些差别。

类加载器(ClassLoader)

就是将类型加载进JVM内存的工具,所有的类型想进入JVM内存就要通过类加载器。

JVM与程序的生命周期

在如下几种情况下,Java虚拟机将结束生命周期

  • 执行了 System.exit() 方法程序正常执行结束。
  • 程序在执行过程中遇到了异常或错误而异常终止。
    • 就是写代码时没有catch异常,导致异常传递到了main方法处,导致JVM和程序终止。
  • 由于操作系统出现错误而导致Java虚拟机进程终止。
    • 这是不可抗力的。

类的加载、连接、初始化、使用和卸载

类的加载

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

连接

  • 验证: 确保被加载的类的正确性。
  • 准备: 为类的静态变量分配内存,并将其初始化为默认值。(int 0,boolean false)
       public static int a = 1;//a并不直接等于1,而是会给其一个默认值0。
    
  • 解析:把类中的符号引用转换为直接引用。
       a = classA;
       c = a;//c引用符号a,在解析时c 会直接指向classA。并不会管a是什么东西。
       //或者说,a和c都是符号,是一种字面量,在运行时要确定具体的内存地址或偏移量。
       //就是将符号变成真正的具体的内存地址或偏移量。
    

初始化

  • 为类的静态变量赋予正确的初始值
      public static int a = 1;//在连接阶段赋予的默认值0,此时会被正确的赋予初始值1
    

使用

  • 比如说根据类创建对象。
  • 调用类的相关方法。
  • 等等

卸载

  • 将内存中的类给清除掉,也就不能在创建对象了。

更进一步

类的加载

类的加载是?

  • 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区(元空间)内,然后在内存中创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构。
  • 无论生成多少个实例,最终的Class对象只有一份。
  • Class对象就像一面镜子,可以洞悉到Class里面的所有内容和结构,其实这就是反射的根源。

类的加载方式是?

  • 从本地系统中直接加载。
  • 通过网络下载.class文件。
  • 从zip,jar等归档文件中加载.class文件。
  • 从专有数据库中提取.class文件。
  • 将Java源文件动态编译为.class文件。
    • 比如动态代理
    • 比如运行jsp时,jsp会被翻译成servlet进一步成为.class

初始化

在说之前, Java程序对类的使用方式有?

  • 主动使用。
  • 被动使用。

什么时候发生初始化?

  • 所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们。
  • 换句话说,被动使用时不初始化类。

主动使用指的是?

  • 创建类的实例。
    new Thread();
    
  • 访问某个类或接口的静态变量,或者对该静态变量赋值。
    System.out.println(StaticClass.value);//访问静态变量。
    StaticClass.value = "hello world";//给静态变量赋值。
    
  • 调用类的静态方法。
    StaticClass.function();
    
  • 反射
    Class.forName("com.test.Test")
    
  • 初始化一个类的子类。
  • Java虚拟机启动时被标明为启动类的类
    • 最常见的main方法所在的类。
    • 还有单元测试,Junit等。
  • JDK1.7开始提供的动态语言支持:
    • java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化。(较罕见)。

被动使用指的是?

  • 除了上述讲的7种主动使用,其它都是被动使用。

用一些代码来说明类的初始化

众所周知,静态代码块在没有new的情况下,是会自动运行的。

  • 代码
public class InitTest {
    public static void main(String[] args) {
        System.out.println(Child.str);
    }
}
//static 代表着Class
class Parent{
     public static String str = "hello world";
     static {
         System.out.println("Parent static block");
     }
}
class Child extends Parent{
     static {
         System.out.println("Child static block");
     }
}
  • 结果

image.png

  • 可以发现子类的静态代码块并没有运行
    • 可以发现子类并没有满足 “首次主动使用” 的条件,它没有被初始化。
    • 父类满足了“主动使用”的条件---访问静态变量(在main方法中访问了str)。
    • 就进一步得出结论,静态代码块是在类加载中的初始化后执行的。
  • 对于静态字段来说,只有直接定义了该字段的类才会被初始化

  • 小小的修改一下代码,访问子类静态变量,让其父类初始化
public class InitTest {
    public static void main(String[] args) {
        System.out.println(Child.string);//访问子类静态变量,导致父类初始化
    }
}
//static 代表着Class
class Parent{
     public static String str = "hello world";
     static {
         System.out.println("Parent static block");
     }
}
class Child extends Parent{
    public static String string = "Welcome to China";
     static {
         System.out.println("Child static block");
     }
}
  • 结果

image.png

  • 当一个类在初始化时,要求其父类全部都已经初始化完毕了。

对于没有被初始化的类,它有被加载吗?

  • 从第一个代码示例可知,只有在类初始化时,才会导致静态代码块的执行。
    • Child类的静态代码块没有执行,也就没有被初始化,那如何知道它有没有被加载嘞?
  • 可以用JVM的运行参数来获取类是否被加载。
    • -XX:+TraceClassLoading 用于追踪类的加载信息并打印出来。

VM选项

image.png

image.png

  • 运行结果

image.png

image.png

  • 学到了,类在被初始化时,才会导致静态代码块的执行。
  • 即使类没有被初始化也会被加载。

关于VM参数

  • 形式是固定的
-XX:+<option>//表示开启option选项
-XX:-<option>//表示关闭option选项

/*
还有常见的调整JVM内存大小的参数,形状如下:
*/
-XX:<option>=<value>//表示将option选项的值设置为value