Java内存管理、GC,包括 Shenandoah ZGC

Java 历史:

Java内存管理、GC,包括 Shenandoah ZGC_第1张图片

Java 技术体系

Java内存管理、GC,包括 Shenandoah ZGC_第2张图片
JVM (Java 虚拟机)
官方: Hotspot
第三方: JRockit,IBM J9
安卓虚拟机: Android Dalvik, ART (由于不符合JVM规范,一般不称为Java虚拟机)

内存

JVM数据区:
Java内存管理、GC,包括 Shenandoah ZGC_第3张图片
程序计数器: 当前线程执行的字节码指令的地址,执行Native方法时为空
不会抛出异常

虚拟机栈: 局部变量表 方法出口等
异常: StackOverflowError OutOfMemoryError
配置:-Xss

本地方法栈: 执行Native方法的栈 (Hotspot本地方法栈共用虚拟机栈)
异常: StackOverflowError OutOfMemoryError

: 对象和数组在堆上分配(有例外,JIT优化),垃圾回收, 物理上可以不连续
异常: OutOfMemoryError
配置:-Xms -Xmx

方法区: 类信息、常量、静态变量、JIT编译后代码, 也叫Non-Heap(非堆)
Java7、8 废弃了永久代
异常:OutOfMemoryError

直接内存:DirectMemory, 非JVM数据区
异常:OutOfMemoryError
注:java.nio.DirectByteBuffer受 -XX:MaxDirectMemorySize 限制,不配置则等于 -Xmx

对象

对象创建
1、检查类加载,未加载则加载类
2、分配内存:有指针碰撞和空闲列表两种方式, 取决于内存是否规整
解决并发的两种方式:CAS失败重试 本地线程分配缓冲TLAB
3、初始化为0值,设置对象头,引用入栈,执行方法

对象内存布局
对象头、实例数据、对齐填充

对象头
1、运行时数据:hashCode GC分代年龄 锁信息
(2、类型指针)
(3、数组长度)

对象访问定位:句柄、直接指针
Hotspot为直接指针
Java内存管理、GC,包括 Shenandoah ZGC_第4张图片
Java内存管理、GC,包括 Shenandoah ZGC_第5张图片

垃圾收集 GC

GC要解决的问题
哪些内存需要回收
什么时候回收
如何回收

衡量GC算法的指标
内存占用
吞吐量: CPU占用
延迟: GC某些阶段需要暂停所有用户线程,称为STW(stop the world),停顿时间越短越好
分配内存效率: 若内存是规整的,可采用指正碰撞分配内存,性能较高

判断垃圾对象算法

  1. 引用计数 (如 Python .com)
  2. 可达性分析 (如 Java Go C#)

由于引用计数存在循环引用问题,Java采用了可达性分析算法。
Java内存管理、GC,包括 Shenandoah ZGC_第6张图片
Root节点:栈引用、静态变量引用、常量引用、虚拟机内部引用等

引用类型
强引用 Strongly:不回收
软引用 Soft: 内存溢出时回收
弱引用 Weak:GC时会回收
虚引用 Phantom:不能通过虚引用获取对象实例,用于回收时接收通知

方法区的回收
回收废弃的常量和不再使用的类型

垃圾收集算法

分代收集
将Java堆划分出不同的区域, 对象依据年龄分配的不同的区域中。

分代收集可行是基于以下假说:
弱分代假说:绝大多数对象都是朝生夕灭的
强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
跨代引用假说:跨代引用相对于同代引用来说仅占极少数。

部分收集(Partial GC): 收集部分Java堆,分为:
1、新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
2、老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
3、混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。

三种具体回收算法:

标记清除 Mark-Sweep:
Java内存管理、GC,包括 Shenandoah ZGC_第7张图片
标记复制 Mark-Copy:
半区复制 Semispace Copying
Java内存管理、GC,包括 Shenandoah ZGC_第8张图片
半区复制会浪费一半内存,由此产生了appel式回收
分为3个区域,一个Eden,两个Survivor
Eden : Survivor : Survivor = 8 : 1 : 1
在Eden和一个Survivor分配对象,收集时复制到另一个Survivor
新生代一般采用appel式回收算法

标记整理 Mark-Compact:
Java内存管理、GC,包括 Shenandoah ZGC_第9张图片

并行(Parallel):多个垃圾收集器线程并行
并发(Concurrent):垃圾收集器线程与用户线程并发

三色标记算法:
Java内存管理、GC,包括 Shenandoah ZGC_第10张图片
三色标记用于解决和用户线程并发扫描的问题。
由于和用户线程并发,标记过程中同时发生以下操作,会导致应该是黑色的对象被误标记为白色:

  • 插入了从黑色对象到白色对象的新引用
  • 删除了全部从灰色对象到该白色对象的直接或间接引用

有两种解决方案:

增量更新(Incremental Update)
当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的对象为根,重新扫描一次。

原始快照(Snapshot At TheBeginning,SATB)
当删除指向白色对象的引用关系时,就将这个引用记录下来,在并发扫描结束之后,再将这些记录过的对象为根,重新扫描一次。

经典垃圾收集器

Java内存管理、GC,包括 Shenandoah ZGC_第11张图片

CMS收集器(Concurrent Mark Sweep)

于Java5发布,Java8 以前最常用的垃圾收集器,以低延迟为目标,网站服务器等关注响应速度的应用一般采用该收集器。Java8之后逐渐被G1收集器取代。
采用分代收集,新生代为标记复制算法,老年代为并发的标记清除算法(名字的由来)。

过程:
初始标记(CMS initial mark) 标记根节点
并发标记(CMS concurrent mark) 并发标记整个堆
重新标记(CMS remark) 采用增量更新解决并发标记过程中用户线程修改对象引用的问题
并发清除(CMS concurrent sweep) 并发的清除垃圾对象

G1收集器(Garbage First)

目标是取代CMS收集器,Java7正式发布,Java8逐渐成熟,并于Java9成为默认收集器。相比CMS具有更低更稳定的延迟时间。
建立了停顿时间模型,即可以配置一个M毫秒的时间,用于垃圾收集的延迟时间大概率不超过M毫秒。
采用了基于分区(Regine)的内存布局,将整个堆分为默认2048个分区,优先收集垃圾最多的分区。同样采用了分代回收,每个分区既可以扮演新生代,也可以扮演老年代。

过程:
初始标记(Initial Marking) 标记根节点
并发标记(Concurrent Marking) 并发标记整个堆
最终标记(Final Marking) 采用原始快照解决并发标记过程中用户线程修改对象引用的问题
筛选回收(Live Data Counting and Evacuation) 回收垃圾最多的分区

低延迟垃圾收集器

Shenandoah 和 ZGC 是新推出的两款低延迟收集器,在TB级别的内存中停顿时间不超过10毫秒。

Shenandoah
Java12 推出,只有 OpenJDK 包含,而 OracleJDK 不包含。
是G1的继承者,同样基于分区,主要的改进是:支持并发的垃圾收集,不分代,采用连接矩阵记录跨分区的引用关系。

过程:
初始标记(Initial Marking) 标记根节点
并发标记(Concurrent Marking) 标记整个堆
最终标记(Final Marking) 处理原始快照扫描,并统计回收价值最高的分区
并发清理(Concurrent Cleanup) 清理一个存活对象都没有的分区
并发回收(Concurrent Evacuation) 并发回收分区,将存活对象复制到其他分区,用到“转发指针”
初始引用更新(Initial Update Reference) 引用更新的准备工作
并发引用更新(Concurrent Update Reference) 把整个堆中指向旧对象的引用修正到复制后的新地址
最终引用更新(Final Update Reference) 更新跟节点引用
并发清理(Concurrent Cleanup) 清理回收过的分区

*转发指针:在对象上加一个指针字段,指向移动后的对象,不移动时指向自己。

ZGC
Java11 推出。是一款基于分区内存布局的,不分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。

染色指针: 指针前4位记录三色标记、被移动信息
Java内存管理、GC,包括 Shenandoah ZGC_第12张图片
多重映射: 将多个不同的虚拟内存地址映射到同一个物理内存地址上

支持“NUMA-Aware”的内存分配

过程:
并发标记(Concurrent Mark) 标记整个堆,更新染色指针中的Marked 0、Marked 1标志位
并发预备重分配(Concurrent Prepare for Relocate) 计算要清理哪些分区
并发重分配(Concurrent Relocate) 存活对象复制到新的分区上,维护转发表
并发重映射(Concurrent Remap) 修正所有引用,释放转发表,该阶段合并到下一次并发标记阶段

Epsilon
只分配内存、不回收内存的收集器,可用于短时间运行的服务。

你可能感兴趣的:(编程)