Java GC 学习小记

1 篇文章 0 订阅


最近看了一篇博文《 代码触发JVM的Full GC和Young GC》,代码触发GC,上手试了一下,分析理解GC,同时再重新整理一下GC的东西,实践一下费曼学习法。

GC 概念

什么是 GC

GC garbage collection,垃圾回收,这里特指 JVM 内存回收。

垃圾回收出现之前,编写代码时需要自行申请和管理内存,容易出现各种内存泄漏,导致程序崩溃。为了解决这个痛点,降低编程门槛,带垃圾回收的编程语言就出现了。它能对不使用的内存进行回收,帮我们实现自动内存管理,减少开发工作。

GC 的基本思路是对使用到的对象进行标记,剩余没标记到的对象就是可清除的,对其进行回收再利用,进而达到持久的内存管理。

标记算法现行的主流是根搜索算法,通过 JVM 中定义的根对象(GC root)开始,从这些不可回收的对象开始,进行有向图遍历把使用的对象进行标记,那么剩余的未标记就是我们回收清除的对象。

常见的根对象包括:
栈中引用的对象 (在用呢)
类的静态属性引用的对象
常量引用的对象
Native方法引用的对象

Java GC 算法

垃圾回收的基础思路是标记清除,基于标记清除算法又优化出了标记整理算法、复制算法以及分代算法。

算法实现特点场景
标记整理标记清除+移动对象整理碎片需要遍历多次,时间效率低,空间利用率高回收变动较小,空间需求大时
复制分成From和To,遍历时复制可用对象至To,遍历完清除From,来回置换遍历1次,时间效率高,空间利用低回收变动频繁
分代标记整理 + 复制,新对象用复制,老对象用标记整理杂糅新生代处理新对象,一般寿命较短;年老代处理旧对象,一般变动较少

Java GC 分类

Java GC 主要是对 JVM 进程的内存管理,尤其是堆(Heap)内存和元空间(Meta Space)的管理。分代算法中存在两种不同的类型,所以主要有YoungGC(也叫Minor GC), FullGC两个。
堆 = 新生代 + 年老代; 默认 1:2
新生代 = Eden + Survivor 0 + Survivor 1(也叫 From, To);默认是 8:1:1
YoungGC 是回收 YoungGen 区域 Eden + S0(From),S1 (To) 区为置空区;
FullGC 是主要内存区域的回收,所以有 YGC(新生代),OldGC(年老代),元空间回收(metaspace)。

简单代码演示

code

这里开篇提到的博文,如何使用代码实现运行时的 5次ygc,然后3次fgc,然后3次ygc,然后1次fgc,请给出代码以及启动参数。
先看代码

public class GCTest {
	private static final int _1MB = 1024 * 1024;
	
	public static void main(String[] args) throws InterruptedException {
		System.out.println("0.---");
		List caches = new ArrayList();
		
		for(int i=0; i<11; i++){
			caches.add(new byte[3*_1MB]);
		}
		
		System.out.println("1.----");
		
		caches.add(new byte[3* _1MB]);
		
		caches.remove(0);
		caches.add(new byte[3*_1MB]);
		
		
		for(int i=0;i<8;i++){
			caches.remove(0);
		}
		caches.add(new byte[3* _1MB]);
		
		System.out.println("2.----");
		
		for(int i=0;i<7;i++){
			caches.add(new byte[3* _1MB]);
		}
	}
}

代码很简单,核心思路就是限制新生代、年老代的大小,通过生成适当大的对象,触发YGC,移动大对象到年老代;之后通过释放部分对象,触发FGC;再释放再触发达到题目的要求。

配置参数

JVM的配置参数时

-Xms41m -Xmx41m -Xmn10m -XX:SurvivorRatio=8 -XX:+UseParallelGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

-X 配置 内存
-XX 配置参数 + 使用该参数 -忽略该参数
在这里插入图片描述
可以看一下GC日志(简略版)

0.080: [GC (Allocation Failure) [PSYoungGen: 7127K->736K(9216K)] 7127K->6888K(41984K), 0.0018287 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.082: [GC (Allocation Failure) [PSYoungGen: 7040K->704K(9216K)] 13192K->13000K(41984K), 0.0019527 secs] [Times: user=0.08 sys=0.02, real=0.00 secs]
0.085: [GC (Allocation Failure) [PSYoungGen: 6980K->688K(9216K)] 19276K->19128K(41984K), 0.0017549 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.087: [GC (Allocation Failure) [PSYoungGen: 6974K->656K(9216K)] 25414K->25240K(41984K), 0.0017696 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.089: [GC (Allocation Failure) [PSYoungGen: 6948K->704K(9216K)] 31532K->31432K(41984K), 0.0017646 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.091: [Full GC (Ergonomics) [PSYoungGen: 704K->0K(9216K)] [ParOldGen: 30728K->31249K(32768K)] 31432K->31249K(41984K), [Metaspace: 2655K->2655K(1056768K)], 0.0068850 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
0.099: [Full GC (Ergonomics) [PSYoungGen: 6296K->3072K(9216K)] [ParOldGen: 31249K->31249K(32768K)] 37545K->34321K(41984K), [Metaspace: 2655K->2655K(1056768K)], 0.0055871 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
0.105: [Full GC (Ergonomics) [PSYoungGen: 6144K->0K(9216K)] [ParOldGen: 31249K->12817K(32768K)] 37393K->12817K(41984K), [Metaspace: 2655K->2655K(1056768K)], 0.0034627 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.109: [GC (Allocation Failure) [PSYoungGen: 6299K->32K(9216K)] 19116K->18993K(41984K), 0.0008367 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.110: [GC (Allocation Failure) [PSYoungGen: 6332K->32K(9216K)] 25293K->25137K(41984K), 0.0007602 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.111: [GC (Allocation Failure) [PSYoungGen: 6305K->32K(9216K)] 31411K->31281K(41984K), 0.0007003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.112: [Full GC (Ergonomics) [PSYoungGen: 32K->0K(9216K)] [ParOldGen: 31249K->31249K(32768K)] 31281K->31249K(41984K), [Metaspace: 2655K->2655K(1056768K)], 0.0013869 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

可增加了 -XX:+PrintHeapAtGC 能够在GC触发前,打印堆得使用信息,方便理解。这里只显示一个

{Heap before GC invocations=1 (full 0):
PSYoungGen total 9216K, used 7127K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 87% used [0x00000000ff600000,0x00000000ffcf5e28,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 32768K, used 0K [0x00000000fd600000, 0x00000000ff600000, 0x00000000ff600000)
object space 32768K, 0% used [0x00000000fd600000,0x00000000fd600000,0x00000000ff600000)
Metaspace used 2654K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 287K, capacity 386K, committed 512K, reserved 1048576K
0.080: [GC (Allocation Failure) [PSYoungGen: 7127K->680K(9216K)] 7127K->6832K(41984K), 0.0018474 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap after GC invocations=1 (full 0):
PSYoungGen total 9216K, used 680K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffe00000)
from space 1024K, 66% used [0x00000000ffe00000,0x00000000ffeaa020,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 32768K, used 6152K [0x00000000fd600000, 0x00000000ff600000, 0x00000000ff600000)
object space 32768K, 18% used [0x00000000fd600000,0x00000000fdc02020,0x00000000ff600000)
Metaspace used 2654K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 287K, capacity 386K, committed 512K, reserved 1048576K
}

GC 日志描述

GC 日志的查看说明,也是网上找的,整理一下,以便后续查用。

[GC (Allocation Failure) [PSYoungGen: 7127K->736K(9216K)] 7127K->6888K(41984K), 0.0018287 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

以YGC来看,格式是

  • [GC …][Times …]
    • [GC [GC信息] GC前堆已用内存 -> GC后堆已用内存 (堆总内存), GC时间]
      • [GC类型: GC前区域已用内存 -> GC后区域已用内存 (区域总内存)]
    • [Times user 用户耗时 sys 系统耗时 real 实际耗时 ]

上面的日志具体信息:分配内存失败,进行YGC。YGC 前新生代已用 7127 K,GC后 已用736K,总大小9216K(Eden + s0),堆(41984K)由7127K -> 6888K, YGC耗时 0.0018287秒。

Full GC 的日志

[Full GC (Ergonomics) [PSYoungGen: 704K->0K(9216K)] [ParOldGen: 30728K->31249K(32768K)] 31432K->31249K(41984K), [Metaspace: 2655K->2655K(1056768K)], 0.0068850 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
  • [GC …][Times …]
    • [GC [YGC信息][FGC信息] GC前堆已用内存 -> GC后堆已用内存 (堆总内存), [元空间回收信息], GC时间]
      • [GC类型: GC前区域已用内存 -> GC后区域已用内存 (区域总内存)]
    • [Times user 用户耗时 sys 系统耗时 real 实际耗时 ]

FullGC: 新生代总大小 9216K,由704K ->0K;年老代大小 32768K,由30728K -> 31249K;元空间105678K,由2655K ->2655K。FGC耗时0.0068850秒,实际耗时约0.01秒

GC分析

根据GC日志可以看出,一共进行了 5YGC 3FGC 3YGC 1FGC。分析之前先看一下
GC悲观策略之Parallel GC篇
GC悲观策略之Serial GC篇
代码触发JVM的Full GC和Young GC

采用Parallel GC的情况下,当YGC触发时,会有两个检查:
1、在YGC执行前,min(目前新生代已使用的大小,之前平均晋升到old的大小中的较小值) > 年老代剩余空间大小 ? 不执行YGC,直接执行Full GC : 执行YGC;
2、在YGC执行后,平均晋升到old的大小 > 年老代剩余空间大小 ? 触发Full GC : 什么都不做。

新生代内存 8M 1M 1M, 年老代内存 31M
代码逻辑,for循环 往list中添加 3MB 的byte数组,因为数量级的原因,可忽略Byte和KB量级的对象。

循环Es0s1oldgc
13000/
26000/
36
0+3
0
0
0
0
0
6
YGC
46006/
56
0+3
0
0
0
0
6
12
YGC
660012/
76
0+3
0
0
0
0
12
18
YGC
860018/
96
0+3
0
0
0
0
18
24
YGC,
old剩余7M, 大于6M,不触发FGC
1060024/
116
0+3
0
0
0
0
24
30
YGC + FGC
YGC 之后满足条件2,触发FGC

查看前6次GC日志,Eden与Old的大小基本与实际的一致(忽略低量级对象)
之后的分析逻辑基本与上述过程类似,达成问题描述。
PS: 这只是针对 Parallel GC 的分析,帮助理解垃圾回收

思考

在实际编码过程中,大对象List或Map耗时操作时,使用完之后,可以及时清除掉。
这样即使所在方法作用域没有执行完成,GC的时候也可以自动回收大对象,否则一旦碰到极端情况,极易引起 old区占满,导致 OOM 问题。

引用

GC悲观策略之Parallel GC篇
GC悲观策略之Serial GC篇
代码触发JVM的Full GC和Young GC
GC总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值