Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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月06日T08:44:49.453+0800
169+
170+
日志打印时间 日期格式 如 2013年05月04日T21: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月20日T17:19:43.794-0800
225+
226+
日志打印时间 日期格式 如 2013年05月04日T21: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)

AltStyle によって変換されたページ (->オリジナル) /