JVM 知识点

xfcoding / 2023-07-17 / 原文

知识点

  1. class 文件结构
  2. classloader
  3. JVM 运行时数据区
  4. 垃圾回收器和垃圾回收算法
  5. JIT

JVM 运行时数据区

运行时数据区是规范的叫法,一般习惯叫做 JVM 内存结构。JVM 的内存结构可以分为公有和私有两部分。公有指的是所有线程都共享的部分,指的是 Java 堆、方法区、常量池。私有指的是每个线程的私有数据,包括:PC寄存器、Java 虚拟机栈、本地方法栈。

公有部分:Java堆、方法区、常量池

1、Java 堆
大部分实例对象在堆中分配内存。小部分小对象直接在栈上分配。
Java 堆根据对象存活时间的不同,Java 堆还被分为年轻代、老年代两个区域,年轻代还被进一步划分为 Eden 区、From Survivor 0、To Survivor 1 区。如下图所示。

当有对象需要分配时,一个对象永远优先被分配在年轻代的 Eden 区,等到 Eden 区域内存不够时,Java 虚拟机会启动垃圾回收。此时 Eden 区中没有被引用的对象的内存就会被回收,而一些存活时间较长的对象则会进入到老年代。在 JVM 中有一个名为 -XX:MaxTenuringThreshold 的参数专门用来设置晋升到老年代所需要经历的 GC 次数,即在年轻代的对象经过了指定次数的 GC 后,将在下次 GC 时进入老年代。

2、方法区
存储Java 字节码文件的数据的一个区域。也就是说,它存储了一个类的结构信息,如常量池、字段和方法数据等。可以看到,常量池其实是存放在方法区中的,但《Java 虚拟机规范》将常量池和方法区放在同一个等级上,这点我们知晓即可。方法区在不同版本的虚拟机有不同的表现形式,例如在 1.7 版本的 HotSpot 虚拟机中,方法区被称为永久代(Permanent Space),而在 JDK 1.8 中则被称之为 MetaSpace。

私有部分:PC寄存器、Java 虚拟机栈、本地方法栈

1、PC 寄存器
Program Counter Register,指的是保存线程当前正在执行的方法。如果这个方法不是 native 方法,那么 PC 寄存器就保存 Java 虚拟机正在执行的字节码指令地址。如果是 native 方法,那么 PC 寄存器保存的值是 undefined。任意时刻,一条 Java 虚拟机线程只会执行一个方法的代码,而这个被线程执行的方法称为该线程的当前方法,其地址被存在 PC 寄存器中。

2、Java 虚拟机栈
Java 虚拟机栈,这个栈与线程同时创建,用来存储栈帧,即存储局部变量与一些过程结果的地方。栈帧存储的数据包括:局部变量表、操作数栈。

3、本地方法栈
当 Java 虚拟机使用其他语言(例如 C 语言)来实现指令集解释器时,也会使用到本地方法栈。

类加载机制

JVM把.class文件加载到内存中时,创建对应的class对象,这个过程称之为类的加载机制。

类的加载过程

Loading -> Linking -> Initializing

加载、连接(验证、准备、解析)、初始化。

  • 加载:查找文件。通过类的全限定名
  • 初始化:为类的静态变量赋值,然后执行类的初始化语句(static代码块)。

初始化过程:

  • 类的初始化是在类的加载和链接完成之后开始,这是固定顺序;
  • 如果存在父类,且父类没有初始化,则先初始化直接父类;
  • 如果类中存在初始化语句,顺序执行初始化语句。

类的初始化的时机:包括主动引用和被动引用。

  • 创建类的实例(四种方式:new、反射、反序列化、克隆)
  • 访问类中的某个静态变量,或者对静态变量进行赋值
  • 调用类的静态方法
  • 反射Class.forName
  • 子类的初始化,会先完成父类的初始化(接口除外)
  • 主动执行main方法

类初始化过程总结

  1. 对类的主动引用:new一个对象、调用类变量、类方法;反射;父类还未初始化时;执行主类(main)。

  2. 被动引用:

(1)子类引用父类静态字段,不会导致子类初始化。

class ClassLoadingTest {
    public static void main(String[] args) {
        System.out.println(SubClass.value);
    }
}

class SuperClass {
    static {
        System.out.println("Superclass init");
    }
    public static int value = 123;
}

class SubClass extends SuperClass {
    static {
        System.out.println("Subclass init");
    }
}

结果是:

Superclass init
123

可以看到子类并未初始化。

(2)通过数组定义来引用类,不会导致类的初始化。

class ClassLoadingTest {
    public static void main(String[] args) {
        SuperClass[] superClasses = new SuperClass[10];
    }
}

这里,main方法的执行结果是什么都不打印。这种情况是类虽然被加载了,但未初始化。

(3)常量在编译阶段存入调用类的常量池中,本质上没有直接引用该类,不会出发类的初始化。

value字段用static final修饰,SuperClass不会初始化,打印结果为:

123

类加载器

类加载是类加载流程的实现者。

JDK自带的Class loader:

  • BootStrap ClassLoader:自带的引导类加载器。由C/C++语言实现。在Java中打印为null,加载Java的核心类库
  • Extension ClassLoaderLauncher的内部类
  • Application ClassLoaderLauncher的内部类

问题:为什么需要自定义classLoader?

  • 适配环境隔离(Tomcat中就有自定义类加载器)
  • 从不同的数据源加载类
  • 防止源码泄露

双亲委派模型

类的加载时,会向上询问是否加载,同时从上向下尝试是否可加载该类。

双亲委派模型的作用:

  1. 避免类的重复加载;

  2. 保护程序安全,防止Java核心语言环境被破坏。

垃圾回收的几种类型

垃圾回收的几种术语:Minor GC、Major GC、Young GC、Old GC、Full GC、Stop-The-World。

  • Minor GC:从年轻代空间回收内存被称为 Minor GC,有时候也称之为 Young GC。
  • Major GC:从老年代空间回收内存被称为 Major GC,有时候也称之为 Old GC。
  • Full GC:Full GC 是清理整个堆空间 —— 包括年轻代、老年代和永久代(如果有的话)。因此 Full GC 可以说是 Minor GC 和 Major GC 的结合。
  • Stop-The-World:是指在进行垃圾回收时因为标记或清理的需要,必须让所有执行任务的线程停止执行任务,从而让垃圾回收线程回收垃圾的时间间隔。

参考资料

http://jvmtutorial.com/#/