JVM 学习笔记

JVM 学习笔记(基于JDK11)

类加载过程

JVM 学习笔记_第1张图片

  1. 加载(Loading):Loading 阶段读取类文件产生二进制流,并转化为特定的数据结构,初步校验 cafe babe 魔法数、常量池、文件长度、是否有父类等,然后创建对应类的 java.lang.Class 对象实例。
  2. 验证(Verification):验证是更详细的校验,比如 final 是否合规、类型是否正确、静态变量是否合理等
  3. 准备(Preparation):准备阶段是为静态变量分配内存,并设定默认值,
  4. 解析(Resolution):解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局。
  5. 初始化(Initialization):Init 阶段执行类构造器方法,如果赋值运算是通过其他类的静态方法来完成的,那么会马上解析另外个类,在虚拟机枪中执行完毕后通过返回值进行赋值。
  6. 使用(Using)
  7. 卸载(Unloading)

注意以下几种情况不会执行类初始化:

  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  2. 定义对象数组,不会触发该类的初始化。
  3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
  4. 通过类名获取Class对象,不会触发类的初始化。
  5. 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
  6. 通过ClassLoader默认的loadClass方法,也不会触发初始化动作。

双亲委派模型

JVM 学习笔记_第2张图片

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。 采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。

自定义类加载器

  1. 继承 ClassLoader 类;
  2. 重写 findClass() 方法;
  3. 调用 defineClass() 方法。

JVM 内存布局

JVM 学习笔记_第3张图片

堆区(Heap)

注意:JDK8 的字符串常量移植到堆内存中

设置堆区初始化参数:

-Xms256M -Xmx1024M
其中 -X 表示是 JVM 运行参数
ms 是 memory start 的简称,代表最小堆容量
mx 是 memory max 的简称,代表最大堆容量
但是在通常情况下,服务在运行过程中,堆空间不断扩大与缩小,势必会形成不必要的系统压力,所以在线上生成环境中,JVM 的 Xms 和 Xmx 设置成一样的大小,避免在 GC 后调整堆大小是带来的额外压力。

堆分成两大块,新生代(Young)和老年代(Old)。

对象产生之初在新生代,步入暮年时进入老年代,但是老年代也接纳在新生代无法容纳的超大对象。新生代=1个Eden区+2个Survivor区。绝大部分对象在Eden区生成,当Eden区装填满的时候,会触发 Young Garbage Collection,即 YGC。垃圾回收的时候,在Eden区实现清除策略,没有被引用的对象则直接回收。依然存活的对象会被移送到Survivor区,这个区真是名副其实的存在。Survivor区分为so和Sl两块内存空间,送到哪块空间呢?每次YGC的时候,它们将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除,交换两块空间的使用状态。如果YGC要移送的对象大于Survivor区容量的上限,则直接移交给老年代。每个对象都有一个计数器,每次YGC都会加l。-XX:MaxTenuringThreshold参数能配置计数器的值到达某个阐值的时候,对象从新生代晋升至老年代。默认值是15,可以在Survivor区交换14次之后,晋升至老年代。如图4-9所示。

如果 Survivor 区无法放下,或者超大对象的闹值超过上限,则尝试在老年代中进行分配;如果老年代也无法放下,则会触发Full Garbage Collection,即FGC 。如果依然无法放下,则抛出OOM。堆内存出现OOM 的概率是所有内存耗尽异常中最高的。出错时的堆内信息对解决问题非常有帮助, 所以给 JVM 设置运行参数 -XX:+HeapDumpOnOutOfMemoryError ,让 JVM 遇到 OOM 异常时能输出堆内信息,特别是对相隔数月才出现的 OOM 异常尤为重要。

JVM 学习笔记_第4张图片

元空间(Metaspace)

在JDK8 里, Perm 区(永久的)中的所有内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间内,

JVM 虚拟机栈(JVM Stack)

虚拟机栈通过压栈和出栈的方式,对每个方法对应的活动栈帧进行运算处理,方法正常执行结束,肯定会跳转到另一个栈帧上。在执行的过程中,如果出现异常,会进行异常回溯,返回地址通过异常处理表确定。栈帧在整个JVM 体系中的地位颇高,包括局部变量表、操作栈、动态连接、方法返回地址等。

局部变量表

局部变量表是存放方法参数和局部变量的区域。

操作栈

操作栈是一个初始状态为空的桶式结构栈。在方法执行过程中,会有各种指令往栈中写人和提取信息。JVM 的执行引擎是基于栈的执行引擎,其中的栈指的就是操作栈。字节码指令集的定义都是基于栈类型的,栈的深度在方法元信息的stack 属性中。

动态连接

每个栈帧中包含一个在常量池中对当前方法的引用, 目的是支持方法调用过程的动态连接。

方法返回地址

方法执行时有两种退出情况:正常退出,即正常执行到任何方法的返回字节码指令,如RETURN 、IRETURN 、ARETURN 等,第二, 异常退出。无论何种退出情况,都将返回至方法当前被调用的位置。方法退出的过程相当于弹出当前栈帧,退出可能有三种方式

  • 返回值压入上层调用枝帧。
  • 异常信息抛给能够处理的枪帧。
  • PC 计数器指向方法调用后的下一条指令。

本地方法栈(Native Method Stacks)

本地方法栈(Native Method Stack)在JVM 内存布局中,也是线程对象私有的,但是虚拟机栈“主内”, 而本地方法栈“主外”。这个“内外”是针对JVM 来说的,本地方法栈为 Native 方法服务。线程开始调用本地方法时,会进入一个不再受JVM约束的世界。本地方法可以通过JNI ( Java Native Interface )来访问虚拟机运行时的数据区,甚至可以调用寄存器,具有和 JVM 相同的能力和权限。当大量本地方法出现时, 势必会削弱 JVM 对系统的控制力,因为它的出错信息都比较黑盒。对于内存不足的情况, 本地方法栈还是会抛出native heap OutOfMemory 。

JNI 类本地方法, 最著名的本地方法应该是 System.currentTimeMillis()

程序计数寄存器(Program Counter Pegister)

在程序计数寄存器( Program Counter Register, PC )中, Register 的命名源于CPU 的寄存器, CPU 只有把数据装载到寄存器才能够运行。寄存器存储指令相关的现场信息,由于CPU 时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等,线程执行或恢复都要依赖程序计数器。程序计数器在各个线程之间互不影响,此区域也不会发生内存溢出异常。

JVM 内存区域

JVM 学习笔记_第5张图片

GC 参数

参考链接:https://blog.csdn.net/SIMBA1949/article/details/99930215

引用类型

强引用

在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM 也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。

软引用

软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。

弱引用

弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,总会回收该对象占用的内存。

虚引用

虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。

垃圾收集算法

  1. 引用计数法
  2. 标记清除法
  3. 标记压缩算法
  4. 复制算法
  5. 分代算法

GC 垃圾收集器

  1. Serial 垃圾收集器(单线程、复制算法)
  2. ParNew 垃圾收集器(Serial+多线程)
  3. Parallel Scavenge 收集器(多线程复制算法、高效)
  4. Serial Old 收集器(单线程标记整理算法 )
  5. Parallel Old 收集器(多线程标记整理算法)
  6. CMS 收集器(多线程标记清除算法)
  7. G1 收集器

你可能感兴趣的:(JVM)