Skip to content

Commit d2e4396

Browse files
author
冰蓝少紫
committed
GC详细说明
1 parent 335b925 commit d2e4396

File tree

1 file changed

+340
-0
lines changed

1 file changed

+340
-0
lines changed

JDK/JVM/复习:GC分类.md

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
2+
## 复习:GC分类
3+
4+
针对 HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(Full GC)
5+
6+
- 部分收集(Partial GC):不是完整收集整个 Java 堆的垃圾收集。其中又分为:
7+
8+
- 新生代收集(Minor GC / Young GC):只是新生代(Eden / S0, S1)的垃圾收集
9+
- 老年代收集(Major GC / Old GC):只是老年代的垃圾收集。目前,只有 CMS GC 会有单独收集老年代的行为。<mark>注意,很多时候 Major GC 会和 Full GC 混淆使用,需要具体分辨是老年代回收还是整堆回收。</mark>
10+
- 混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。目前,只有 G1 GC 会有这种行为
11+
- 整堆收集(Full GC):收集整个 java 堆和方法区的垃圾收集。
12+
13+
1. 新生代收集:只有当Eden区满的时候就会进行新生代收集,所以新生代收集和S0区域和S1区域情况无关
14+
15+
2. 老年代收集和新生代收集的关系:进行老年代收集之前会先进行一次年轻代的垃圾收集,原因如下:一个比较大的对象无法放入新生代,那它自然会往老年代去放,如果老年代也放不下,那会先进行一次新生代的垃圾收集,之后尝试往新生代放,如果还是放不下,才会进行老年代的垃圾收集,之后在往老年代去放,这是一个过程,我来说明一下为什么需要往老年代放,但是放不下,而进行新生代垃圾收集的原因,这是因为新生代垃圾收集比老年代垃圾收集更加简单,这样做可以节省性能
16+
17+
3. 进行垃圾收集的时候,堆包含新生代、老年代、元空间/永久代:可以看出Heap后面包含着新生代、老年代、元空间,但是我们设置堆空间大小的时候设置的只是新生代、老年代而已,元空间是分开设置的
18+
19+
4. 哪些情况会触发Full GC:
20+
- 老年代空间不足
21+
- 方法区空间不足
22+
- 显示调用System.gc()
23+
- Minior GC进入老年代的数据的平均大小 大于 老年代的可用内存
24+
- 大对象直接进入老年代,而老年代的可用空间不足
25+
26+
27+
28+
## 不同GC分类的GC细节
29+
30+
用例代码:
31+
32+
```Java
33+
/**
34+
* -XX:+PrintCommandLineFlags
35+
*
36+
* -XX:+UseSerialGC:表明新生代使用Serial GC ,同时老年代使用Serial Old GC
37+
*
38+
* -XX:+UseParNewGC:标明新生代使用ParNew GC
39+
*
40+
* -XX:+UseParallelGC:表明新生代使用Parallel GC
41+
* -XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC
42+
* 说明:二者可以相互激活
43+
*
44+
* -XX:+UseConcMarkSweepGC:表明老年代使用CMS GC。同时,年轻代会触发对ParNew 的使用
45+
* @author shkstart
46+
* @create 17:19
47+
*/
48+
public class GCUseTest {
49+
public static void main(String[] args) {
50+
ArrayList<byte[]> list = new ArrayList<>();
51+
52+
while(true){
53+
byte[] arr = new byte[1024 * 10];//10kb
54+
list.add(arr);
55+
// try {
56+
// Thread.sleep(5);
57+
// } catch (InterruptedException e) {
58+
// e.printStackTrace();
59+
// }
60+
}
61+
}
62+
}
63+
```
64+
65+
### 老年代使用CMS GC
66+
67+
**GC设置方法**:参数中使用-XX:+UseConcMarkSweepGC,说明老年代使用CMS GC,同时年轻代也会触发对ParNew的使用,因此添加该参数之后,新生代使用ParNew GC,而老年代使用CMS GC,整体是并发垃圾收集,主打低延迟
68+
69+
![image-20220419202643](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202551.png)
70+
71+
打印出来的GC细节:
72+
73+
![image-20220419211943](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419211943.png)
74+
75+
76+
77+
### 新生代使用Serial GC
78+
79+
**GC设置方法**:参数中使用-XX:+UseSerialGC,说明新生代使用Serial GC,同时老年代也会触发对Serial Old GC的使用,因此添加该参数之后,新生代使用Serial GC,而老年代使用Serial Old GC,整体是串行垃圾收集
80+
81+
![image-20220419212907](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419212907.png)
82+
83+
打印出来的GC细节:
84+
85+
![image-20220419212940](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419212940.png)
86+
87+
DefNew代表新生代使用Serial GC,然后Tenured代表老年代使用Serial Old GC
88+
89+
## GC 日志分类
90+
91+
### MinorGC
92+
93+
MinorGC(或 young GC 或 YGC)日志:
94+
95+
```java
96+
[GC (Allocation Failure) [PSYoungGen: 31744K->2192K (36864K) ] 31744K->2200K (121856K), 0.0139308 secs] [Times: user=0.05 sys=0.01, real=0.01 secs]
97+
```
98+
99+
![image-20220419202643](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202643.png)
100+
101+
![image-20220419202718](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202718.png)
102+
103+
### FullGC
104+
105+
```java
106+
[Full GC (Metadata GC Threshold) [PSYoungGen: 5104K->0K (132096K) ] [Par01dGen: 416K->5453K (50176K) ]5520K->5453K (182272K), [Metaspace: 20637K->20637K (1067008K) ], 0.0245883 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]
107+
```
108+
109+
![image-20220419202740](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202740.png)
110+
111+
![image-20220419202804](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202804.png)
112+
113+
## GC 日志结构剖析
114+
115+
### 透过日志看垃圾收集器
116+
117+
- Serial 收集器:新生代显示 "[DefNew",即 Default New Generation
118+
119+
- ParNew 收集器:新生代显示 "[ParNew",即 Parallel New Generation
120+
121+
- Parallel Scavenge 收集器:新生代显示"[PSYoungGen",JDK1.7 使用的即 PSYoungGen
122+
123+
- Parallel Old 收集器:老年代显示"[ParoldGen"
124+
125+
- G1 收集器:显示”garbage-first heap“
126+
127+
### 透过日志看 GC 原因
128+
129+
- Allocation Failure:表明本次引起 GC 的原因是因为新生代中没有足够的区域存放需要分配的数据
130+
- Metadata GCThreshold:Metaspace 区不够用了
131+
- FErgonomics:JVM 自适应调整导致的 GC
132+
- System:调用了 System.gc()方法
133+
134+
### 透过日志看 GC 前后情况
135+
136+
通过图示,我们可以发现 GC 日志格式的规律一般都是:GC 前内存占用-> GC 后内存占用(该区域内存总大小)
137+
138+
```java
139+
[PSYoungGen: 5986K->696K (8704K) ] 5986K->704K (9216K)
140+
```
141+
142+
- 中括号内:GC 回收前年轻代堆大小,回收后大小,(年轻代堆总大小)
143+
144+
- 括号外:GC 回收前年轻代和老年代大小,回收后大小,(年轻代和老年代总大小)
145+
146+
<mark>注意</mark>:Minor GC 堆内存总容量 = 9/10 年轻代 + 老年代。原因是 Survivor 区只计算 from 部分,而 JVM 默认年轻代中 Eden 区和 Survivor 区的比例关系,Eden:S0:S1=8:1:1。
147+
148+
### 透过日志看 GC 时间
149+
150+
GC 日志中有三个时间:user,sys 和 real
151+
152+
- user:进程执行用户态代码(核心之外)所使用的时间。这是执行此进程所使用的实际 CPU 时间,其他进程和此进程阻塞的时间并不包括在内。在垃圾收集的情况下,表示 GC 线程执行所使用的 CPU 总时间。
153+
- sys:进程在内核态消耗的 CPU 时间,即在内核执行系统调用或等待系统事件所使用的 CPU 时间
154+
- real:程序从开始到结束所用的时钟时间。这个时间包括其他进程使用的时间片和进程阻塞的时间(比如等待 I/O 完成)。对于并行 gc,这个数字应该接近(用户时间+系统时间)除以垃圾收集器使用的线程数。
155+
156+
由于多核的原因,一般的 GC 事件中,real time 是小于 sys time + user time 的,因为一般是多个线程并发的去做 GC,所以 real time 是要小于 sys + user time 的。如果 real > sys + user 的话,则你的应用可能存在下列问题:IO 负载非常重或 CPU 不够用。
157+
158+
## Minor GC 日志解析
159+
160+
### 日志格式
161+
162+
```Java
163+
2021-09-06T08:44:49.453+0800: 4.396: [GC (Allocation Failure) [PSYoungGen: 76800K->8433K(89600K)] 76800K->8449K(294400K), 0.0060231 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
164+
```
165+
166+
### 日志解析
167+
168+
#### 2021-09-06T08:44:49.453+0800
169+
170+
日志打印时间 日期格式 如 2013-05-04T21:53:59.234+0800
171+
172+
添加-XX:+PrintGCDateStamps参数
173+
174+
#### 4.396
175+
176+
gc 发生时,Java 虚拟机启动以来经过的秒数
177+
178+
添加-XX:+PrintGCTimeStamps该参数
179+
180+
#### [GC (Allocation Failure)
181+
182+
发生了一次垃圾回收,这是一次 Minor GC。它不区分新生代 GC 还是老年代 GC,括号里的内容是 gc 发生的原因,这里 Allocation Failure 的原因是新生代中没有足够区域能够存放需要分配的数据而失败。
183+
184+
#### [PSYoungGen: 76800K->8433K(89600K)]
185+
186+
**PSYoungGen**:表示GC发生的区域,区域名称与使用的GC收集器是密切相关的
187+
188+
- **Serial收集器**:Default New Generation 显示Defnew
189+
- **ParNew收集器**:ParNew
190+
- **Parallel Scanvenge收集器**:PSYoung
191+
- 老年代和新生代同理,也是和收集器名称相关
192+
193+
**76800K->8433K(89600K)**:GC前该内存区域已使用容量->GC后盖区域容量(该区域总容量)
194+
195+
- 如果是新生代,总容量则会显示整个新生代内存的9/10,即eden+from/to区
196+
- 如果是老年代,总容量则是全身内存大小,无变化
197+
198+
#### 76800K->8449K(294400K)
199+
200+
虽然本次是Minor GC,只会进行新生代的垃圾收集,但是也肯定会打印堆中总容量相关信息
201+
202+
在显示完区域容量GC的情况之后,会接着显示整个堆内存区域的GC情况:GC前堆内存已使用容量->GC后堆内存容量(堆内存总容量),并且堆内存总容量 = 9/10 新生代 + 老年代,然后堆内存总容量肯定小于初始化的内存大小
203+
204+
#### ,0.0088371
205+
206+
整个GC所花费的时间,单位是秒
207+
208+
#### [Times:user=0.02 sys=0.01,real=0.01 secs]
209+
210+
- **user**:指CPU工作在用户态所花费的时间
211+
- **sys**:指CPU工作在内核态所花费的时间
212+
- **real**:指在此次事件中所花费的总时间
213+
214+
## Full GC 日志解析
215+
216+
### 日志格式
217+
218+
```Java
219+
2021-09-06T08:44:49.453+0800: 4.396: [Full GC (Metadata GC Threshold) [PSYoungGen: 10082K->0K(89600K)] [ParOldGen: 32K->9638K(204800K)] 10114K->9638K(294400K), [Metaspace: 20158K->20156K(1067008K)], 0.0149928 secs] [Times: user=0.06 sys=0.02, real=0.02 secs]
220+
```
221+
222+
### 日志解析
223+
224+
#### 2020-11-20T17:19:43.794-0800
225+
226+
日志打印时间 日期格式 如 2013-05-04T21:53:59.234+0800
227+
228+
添加-XX:+PrintGCDateStamps参数
229+
230+
#### 1.351
231+
232+
gc 发生时,Java 虚拟机启动以来经过的秒数
233+
234+
添加-XX:+PrintGCTimeStamps该参数
235+
236+
#### Full GC(Metadata GCThreshold)
237+
238+
括号中是gc发生的原因,原因:Metaspace区不够用了。
239+
除此之外,还有另外两种情况会引起Full GC,如下:
240+
241+
1. Full GC(FErgonomics)
242+
原因:JVM自适应调整导致的GC
243+
2. Full GC(System)
244+
原因:调用了System.gc()方法
245+
246+
#### [PSYoungGen: 100082K->0K(89600K)]
247+
248+
**PSYoungGen**:表示GC发生的区域,区域名称与使用的GC收集器是密切相关的
249+
250+
- **Serial收集器**:Default New Generation 显示DefNew
251+
- **ParNew收集器**:ParNew
252+
- **Parallel Scanvenge收集器**:PSYoungGen
253+
- 老年代和新生代同理,也是和收集器名称相关
254+
255+
**10082K->0K(89600K)**:GC前该内存区域已使用容量->GC该区域容量(该区域总容量)
256+
257+
- 如果是新生代,总容量会显示整个新生代内存的9/10,即eden+from/to区
258+
259+
- 如果是老年代,总容量则是全部内存大小,无变化
260+
261+
#### ParOldGen:32K->9638K(204800K)
262+
263+
老年代区域没有发生GC,因此本次GC是metaspace引起的
264+
265+
#### 10114K->9638K(294400K),
266+
267+
在显示完区域容量GC的情况之后,会接着显示整个堆内存区域的GC情况:GC前堆内存已使用容量->GC后堆内存容量(堆内存总容量),并且堆内存总容量 = 9/10 新生代 + 老年代,然后堆内存总容量肯定小于初始化的内存大小
268+
269+
#### [Meatspace:20158K->20156K(1067008K)],
270+
271+
metaspace GC 回收2K空间
272+
273+
274+
275+
## 论证FullGC是否会回收元空间/永久代垃圾
276+
277+
```Java
278+
/**
279+
* jdk6/7中:
280+
* -XX:PermSize=10m -XX:MaxPermSize=10m
281+
* <p>
282+
* jdk8中:
283+
* -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
284+
*
285+
* @author IceBlue
286+
* @create 2020 22:24
287+
*/
288+
public class OOMTest extends ClassLoader {
289+
public static void main(String[] args) {
290+
int j = 0;
291+
try {
292+
for (int i = 0; i < 100000; i++) {
293+
OOMTest test = new OOMTest();
294+
//创建ClassWriter对象,用于生成类的二进制字节码
295+
ClassWriter classWriter = new ClassWriter(0);
296+
//指明版本号,修饰符,类名,包名,父类,接口
297+
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
298+
//返回byte[]
299+
byte[] code = classWriter.toByteArray();
300+
//类的加载
301+
test.defineClass("Class" + i, code, 0, code.length);//Class对象
302+
test = null;
303+
j++;
304+
}
305+
} finally {
306+
System.out.println(j);
307+
}
308+
}
309+
}
310+
```
311+
312+
输出结果:
313+
314+
```
315+
[GC (Metadata GC Threshold) [PSYoungGen: 10485K->1544K(152576K)] 10485K->1552K(500736K), 0.0011517 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
316+
[Full GC (Metadata GC Threshold) [PSYoungGen: 1544K->0K(152576K)] [ParOldGen: 8K->658K(236544K)] 1552K->658K(389120K), [Metaspace: 3923K->3320K(1056768K)], 0.0051012 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
317+
[GC (Metadata GC Threshold) [PSYoungGen: 5243K->832K(152576K)] 5902K->1490K(389120K), 0.0009536 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
318+
319+
-------省略N行-------
320+
321+
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(2427904K)] [ParOldGen: 824K->824K(5568000K)] 824K->824K(7995904K), [Metaspace: 3655K->3655K(1056768K)], 0.0041177 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
322+
Heap
323+
PSYoungGen total 2427904K, used 0K [0x0000000755f80000, 0x00000007ef080000, 0x00000007ffe00000)
324+
eden space 2426880K, 0% used [0x0000000755f80000,0x0000000755f80000,0x00000007ea180000)
325+
from space 1024K, 0% used [0x00000007ea180000,0x00000007ea180000,0x00000007ea280000)
326+
to space 1536K, 0% used [0x00000007eef00000,0x00000007eef00000,0x00000007ef080000)
327+
ParOldGen total 5568000K, used 824K [0x0000000602200000, 0x0000000755f80000, 0x0000000755f80000)
328+
object space 5568000K, 0% used [0x0000000602200000,0x00000006022ce328,0x0000000755f80000)
329+
Metaspace used 3655K, capacity 4508K, committed 9728K, reserved 1056768K
330+
class space used 394K, capacity 396K, committed 2048K, reserved 1048576K
331+
332+
进程已结束,退出代码0
333+
334+
```
335+
336+
通过不断地动态生成类对象,输出GC日志
337+
338+
根据GC日志我们可以看出当元空间容量耗尽时,会触发FullGC,而每次FullGC之前,至会进行一次MinorGC,而MinorGC只会回收新生代空间;
339+
340+
只有在FullGC时,才会对新生代,老年代,永久代/元空间全部进行垃圾收集

0 commit comments

Comments
 (0)