DOIFOR技术JVM内存结构与GC
DOIFOR技术JVM内存结构与GC
jVM内存结构

在谈论GC之前,我觉得有必要将Java的内存模型梳理清除,明白GC到底是清除的那一部分数据。

JVM内存结构

JVM的内存结构中,主要组成部分如下:

  • 堆:堆是JMM中最大的一个区域,存放着程序创建的(几乎)所有的对象。
    • 新生代(伊甸园、幸存区1、幸存区2)、老年代
    • 字符串常量池、静态变量、数组对象也都是存放在堆中的

    • 字符串常量池是在1.7的时候从老年代移除单独放置到堆中的

    • 线程分配缓冲区是什么?
    • 堆是GC主要管理的内存区域
  • 元数据(方法,产量等):在Java8之前没有这一区域,主要用于存放方法、常量等不可变的信息

    • java7以及之前,分为常量区、方法区。常量区用于保存字符串、字面量等数据;方法区也成为永久代,直接使用堆内存。
    • 类信息(版本、字段、方法、接口)和运行时常量池(字面量和符号引用)
  • 方法栈:方法栈是一个封闭、连续的区块,一般来说一个线程对应着一个方法栈。在栈上会存储当前程序的中间状态、临时变量等数据。
    • JVM方法栈
    • native方法栈
    • 方法栈主要包含局部变量、操作数、动态链接、方法返回地址等等信息。
  • 程序计数器:可以理解成类似CPU的寄存器,用于记录程序当前执行字节码行号。
  • 直接内存:主要使用DirectByteBuffer直接操作本地内存,比如内存映射。

其中,堆和元数据是线程共享区域,方法栈和程序计数器是线程私有区域。

JVM调优的重要思想和参数

宗旨:不到万不得已,一般不进行JVM调优

有一些极端的程序,确实需要特别大的堆、或者特别大的元空间、或者频繁创建销毁对象等。我们需要针对其特殊性调整部分参数:

  • -Xms:初始堆大小
  • -Xmx:最大堆大小
  • -Xmn:新生代大小(伊甸园、幸存区)
  • -XX:NewRatio: 老年代与新生代的比例(比如2,代表老年代:新生代=2:1)
  • -XX:SurvivorRatio: 伊甸园和幸存区比例(比如8, 代表伊甸园:幸存区1:幸存区2 = 8:1:1)
  • -XX:MetasapceSize: 元空间初始大小
  • -XX:MaxMetaspaceSize: 元空间最大大小,默认是无上限的,但是可能会导致本地内存溢出

JMM(Java内存模型)

Java内存模型是一套抽象的规范,JVM内存结构是一种物理实现。JMM主要定义了在多线程场景中变量的可见性和有序性,主要解决:

  • 可见性:一个线程的修改何时对其他线程可见
  • 有序性:执行顺序是否与代码顺序一致
  • 原子性:对变量的操作是否不可中断

设计目标:在线程安全的前提下最大程度使用硬件性能,比如使用缓存、指令重排等

垃圾回收(GC)

在现代语言中,对于内存的回收大致有两种处理方式,一种是依赖程序员手动进行回收,比如C/C++、Rust等,这一类语言编写程序需要程序自行考虑对象的生命周期、关注内存的申请和回收时机,稍有不慎就会导致内存溢出等问题,但另一方面这类程序性能普遍较高,所以一般适用于系统底层、对性能极度敏感的场景。

另一种方式就是垃圾回收器自动回收,比如java、c#、go等语言,都有自己的垃圾回收算法。我们这里主要讨论一下java的垃圾回收算法。

Java中的垃圾回收算法整体上可以三类:

  • 标记-删除:标记出能需要回收的对象,然后直接原地删除。这种算法会导致内存碎片化。
  • 标记-复制:由于标记删除算法会有碎片,因此可以标记处需要回收的对象后,将存活下来的对象复制到另一块内存中(连续存放),然后直接删除原来的内存区块。这种算法需要较大的内存空间。
  • 标记-压缩:因此有了标记整理算法,将需要回收的对象标记出来后,对剩余对象进行整理,依次向前移动,剩余空间直接释放。

上述三种算法都有一个共同点,需要stop-the-world(STW)。随着内存的变大,STW的时间会变长,会降低程序的整体性能。因此,有了新的CMS和G1回收器。

三色标记法

三色标记法就是用黑白灰标记对象的三种状态(黑:可达、灰:可达(子节点未完全扫描完)、白:未扫描(或者垃圾))。

  1. 先STW获取到根路径可达实例(标记为黑色),

  2. 然后从根路径可达结点开始并行扫描可达节点,对非叶子节点标记为黑色,叶子节点标记为灰色,扫描完后。所有幸存对象标记为黑色、垃圾为白色

  3. 重新标记阶段:增加更新;原始快照;用于处理并发标记过程中产生的浮动垃圾
  4. 并发清除阶段:不需要STW

浮动垃圾就是在并发标记阶段新产生的实例未被扫描到的情况,如果对这些对象进行标记,就会在清理阶段被回收,导致本不应该回收的对象被回收。这种情况很严重,所以必须被解决。

在三色标记法中针对浮动垃圾的处理会在重新标记阶段进行,CMS和G1的处理方法有所不同,CMS使用的是增加更新,G1采用的原始快照法。

增量更新就是在重新标记阶段,从所有黑色节点开始扫描,重新进行标记。考虑到并发标记过程产生的对象并不回答太多的情况,这种增量更新的方法所需时间也还是能接收。增量更新后,剩余的白色都是垃圾,获取到能够被回收的对象列表,直接回收该列表对象即可。

原始快照发就是在第一阶段生成一个快照,并发标记结束后,快照中的白色就是垃圾,可以直接进行删除。这种方法会将浮动垃圾留给下一次GC处理。

G1中没有明显的分代的概念,主要依赖多个相同大小的region并发回收。CMS有明显的分代概念,在1.9的时候弃用了。

ZGC中使用了着色指针,可变大小region、并行复制回收等方法提高了GC性能,将STW时间降低到压秒级。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注