2020-11-03 | Java | Unlock

JVM-day02

1.如何判断对象可以回收

1.1 引用计数法

JVM-day02-01.png

弊端:循环引用

1.2 可达性分析算法

  1. Java虚拟机中的垃圾回收器采用 可达性分析 来探索所有存活的对象
  2. 扫描堆中的对象,看是否能够沿着GC Root对象 为起点的引用链找到该对象,找不到则表示可以回收
  3. 哪些对象可以作为GC Root?

Java堆分析器 : MAT(Eclipse Memory Analyzer) 是一个快速且功能丰富的Java堆分析器,可帮助您查找内存泄漏并减少内存消耗。使用Memory Analyzer分析具有数亿个对象的高效堆转储,快速计算对象的保留大小,查看谁阻止垃圾收集器收集对象,运行报告以自动提取泄漏嫌疑者。

下载地址:https://www.eclipse.org/mat/downloads.php

功能:

  1. 找出内存泄漏的原因
  2. 找出重复引用的类和jar
  3. 分析集合的使用
  4. 分析类加载器

演示如下:

1
2
3
4
5
6
7
8
9
10
List<Object> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
System.out.println(1);
System.in.read();

list1 = null;
System.out.println(2);
System.in.read();
System.out.println("end...");

运行以上代码,使用 jps 指令找到当前的进程ID

1
2
3
4
5
15136
13604 Jps
15772
15948 Launcher
3612 TestDemo01

出现 1 时,使用如下的jmap指令

1
jmap -dump:format=b,live,file=1.bin 3612

简单解释下format(转储文件的格式)的参数:

b: 二进制文件
live: 只抓取存活的类型,live前会进行一次垃圾回收FullGC
file: 存储文件路径

出现 2 时,使用如下的jmap指令

1
jmap -dump:format=b,live,file=2.bin 3612

这样可以看到在当前目录下有两个文件,1.bin和2.bin

在MAT工具中分别打开这两个bin文件,如下图:

JVM-day02-02.png

打开GC Roots 如下:

JVM-day02-03.png

1.3 四种引用 + 终结器引用

JVM-day02-04.png

  1. 强引用:

    只有所有GC Roots对象都不通过 强引用 引用该对象,该对象才能被垃圾回收

  2. 软引用(SoftReference)

    (1)仅有 软引用 引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象

    (2)可以配合引用队列来释放软引用自身

软引用的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//打印参数: -Xmx20m -XX:+PrintGCDetails -verbose:gc
public class TestDemo02 {

private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) throws IOException {
//强引用
/*List<byte[]> list1 = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list1.add(new byte[_4MB]);
}
System.in.read();*/

//软引用
soft();
}

private static void soft(){
// list --> SoftReference --> byte[]
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> reference = new SoftReference<>(new byte[_4MB]);
System.out.println(reference.get());
list.add(reference);
System.out.println(list.size());
}
System.out.println("循环结束:" + list.size());
for (SoftReference<byte[]> ref : list){
System.out.println(ref.get());
}
}
}

打印结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[[email protected]
1
[[email protected]
2
[[email protected]
3
[GC (Allocation Failure) [PSYoungGen: 2913K->504K(6144K)] 15201K->13317K(19968K), 0.0237151 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
[Full GC (Ergonomics) [PSYoungGen: 504K->0K(6144K)] [ParOldGen: 12813K->13268K(13824K)] 13317K->13268K(19968K), [Metaspace: 3272K->3272K(1056768K)], 0.0235688 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[[email protected]
4
[Full GC (Ergonomics) [PSYoungGen: 4208K->4096K(6144K)] [ParOldGen: 13268K->13117K(13824K)] 17477K->17213K(19968K), [Metaspace: 3274K->3274K(1056768K)], 0.0060131 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4096K->0K(6144K)] [ParOldGen: 13117K->811K(7680K)] 17213K->811K(13824K), [Metaspace: 3274K->3274K(1056768K)], 0.0081056 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[[email protected]
5
循环结束:5
null
null
null
null
[[email protected]
Heap
PSYoungGen total 6144K, used 4377K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 77% used [0x00000000ff980000,0x00000000ffdc6400,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7680K, used 811K [0x00000000fec00000, 0x00000000ff380000, 0x00000000ff980000)
object space 7680K, 10% used [0x00000000fec00000,0x00000000feccac50,0x00000000ff380000)
Metaspace used 3280K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 355K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

加入引用队列ReferenceQueue:移除无用的软引用对象

以上的代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class TestDemo02 {

//打印参数: -Xmx20m -XX:+PrintGCDetails -verbose:gc
private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) throws IOException {
//强引用
/*List<byte[]> list1 = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list1.add(new byte[_4MB]);
}
System.in.read();*/

//软引用
soft();
}

private static void soft(){
// list --> SoftReference --> byte[]
List<SoftReference<byte[]>> list = new ArrayList<>();

//引用队列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

for (int i = 0; i < 5; i++) {
//关联引用队列: 当软引用所关联的byte[]被回收时,软引用自己会加入到queue中去
SoftReference<byte[]> reference = new SoftReference<>(new byte[_4MB],queue);
System.out.println(reference.get());
list.add(reference);
System.out.println(list.size());
}

//从引用队列中获取无用的软引用对象 并移除
Reference<? extends byte[]> poll = queue.poll();
while (poll != null){
list.remove(poll);
poll = queue.poll();
}
System.out.println("===================================");
for (SoftReference<byte[]> ref : list){
System.out.println(ref.get());
}
}
}

打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[[email protected]
1
[[email protected]
2
[[email protected]
3
[GC (Allocation Failure) [PSYoungGen: 2913K->504K(6144K)] 15201K->13329K(19968K), 0.0018256 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 504K->0K(6144K)] [ParOldGen: 12825K->13268K(13824K)] 13329K->13268K(19968K), [Metaspace: 3274K->3274K(1056768K)], 0.0057513 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[[email protected]
4
[Full GC (Ergonomics) [PSYoungGen: 4208K->4096K(6144K)] [ParOldGen: 13268K->13117K(13824K)] 17477K->17213K(19968K), [Metaspace: 3274K->3274K(1056768K)], 0.0078122 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4096K->0K(6144K)] [ParOldGen: 13117K->811K(7680K)] 17213K->811K(13824K), [Metaspace: 3274K->3274K(1056768K)], 0.0058282 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[[email protected]
5
===================================
[[email protected]
Heap
PSYoungGen total 6144K, used 4377K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 77% used [0x00000000ff980000,0x00000000ffdc6400,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7680K, used 811K [0x00000000fec00000, 0x00000000ff380000, 0x00000000ff980000)
object space 7680K, 10% used [0x00000000fec00000,0x00000000feccad40,0x00000000ff380000)
Metaspace used 3281K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 355K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0
  1. 弱引用(WeakReference)

    (1)仅有 弱引用 引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象

    (2)可以配合引用队列来释放弱引用自身

弱引用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//打印参数: -Xmx20m -XX:+PrintGCDetails -verbose:gc
public class TestDemo03 {
private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) throws IOException {
// list --> WeakReference --> byte[]
List<WeakReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
WeakReference<byte[]> reference = new WeakReference<>(new byte[_4MB]);
list.add(reference);
for (WeakReference<byte[]> w : list){
System.out.println(w.get()+" ");
}
System.out.println();
}
System.out.println("循环结束:"+list.size());
}
}

打印结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[[email protected]7ba4f24f 

[[email protected]7ba4f24f
[[email protected]3b9a45b3

[[email protected]7ba4f24f
[[email protected]3b9a45b3
[[email protected]7699a589

[GC (Allocation Failure) [PSYoungGen: 2913K->488K(6144K)] 15201K->13301K(19968K), 0.0018384 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 488K->0K(6144K)] [ParOldGen: 12813K->980K(11776K)] 13301K->980K(17920K), [Metaspace: 3274K->3274K(1056768K)], 0.0049699 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
null
null
null
[[email protected]58372a00

null
null
null
[[email protected]58372a00
[[email protected]4dd8dc3

循环结束:5
Heap
PSYoungGen total 6144K, used 4265K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 75% used [0x00000000ff980000,0x00000000ffdaa5b8,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 11776K, used 5076K [0x00000000fec00000, 0x00000000ff780000, 0x00000000ff980000)
object space 11776K, 43% used [0x00000000fec00000,0x00000000ff0f53b0,0x00000000ff780000)
Metaspace used 3281K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 355K, capacity 388K, committed 512K, reserved 1048576K
  1. 虚引用(PhantomReference)

    必须配合引用队列来使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队列,由ReferenceHandler线程调用虚引用相关方法释放直接内存

  2. 终结器引用(FinalReference)

    无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队列(被引用对象暂时没有被回收),再由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次GC时才能回收被引用对象

2.垃圾回收算法

2.1 标记清除

JVM-day02-05.png

定义: Mark Sweep

优点: 速度较快

缺点: 容易产生内存碎片

2.2 标记整理

JVM-day02-06.png

定义: Mark Compact

优点: 没有内存碎片

缺点: 速度慢

2.3 复制

JVM-day02-07.png

定义: Copy

优点: 没有内存碎片

缺点: 需要占用双倍的内存空间

3.分代垃圾回收

JVM-day02-08.png

MinorGC:新生代垃圾回收,包括Eden和SurvivorFrom以及SurvivorTo
MajorGC:老年代垃圾回收
FullGC:整个堆空间的垃圾回收,包括新生代和老年代
  1. 对象首先分配在Eden区域
  2. 新生代空间不足时,触发MinorGC,Eden和From存活的对象使用copy复制到To中,存活的对象年龄加1并且交换From和To
  3. MinorGC会引发stop the world,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行
  4. 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
  5. 当老年代空间不足,会先尝试触发MinorGC,如果之后空间仍不足,那么触发FullGC,STW的时间更长

3.1 相关JVM参数

含义 参数
堆初始大小 -Xms
堆最大大小 -Xmx或者-XX:MaxHeapSize=size
新生代大小 -Xmn或者-XX:NewSize=size和-XX:MaxNewSize=size
幸存区Survivor比例(动态) -XX:InitialSurvivorRatio=ratio和-XX:+UseAdaptiveSizePolicy
幸存区Survivor比例 -XX:SurvivorRatio=ratio
晋升阈值 -XX:MaxTenuringThreshold=threshold
晋升详情 -XX:+PrintTenuringDistribution
GC详情 -XX:+PrintGCDetails -verbose:gc
FullGC前MinorGC -XX:+ScavengeBeforeFullGC

3.2 GC分析

分析代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
//参数: -Xms20m -Xmx20m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
public class TestDemo04 {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 1 * 1024 * 1024;
private static final int _6MB = 6 * 1024 * 1024;
private static final int _7MB = 7 * 1024 * 1024;
private static final int _8MB = 8 * 1024 * 1024;

public static void main(String[] args) {

}
}

打印结果如下:

1
2
3
4
5
6
7
8
9
Heap
def new generation total 9216K, used 2982K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 36% used [0x00000000fec00000, 0x00000000feee9818, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3273K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 354K, capacity 388K, committed 512K, reserved 1048576K

由以上的结果分析,并未发生GC

这里继续分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
//参数: -Xms20m -Xmx20m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
public class TestDemo04 {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 1 * 1024 * 1024;
private static final int _6MB = 6 * 1024 * 1024;
private static final int _7MB = 7 * 1024 * 1024;
private static final int _8MB = 8 * 1024 * 1024;

public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
list.add(new byte[_7MB]);
}
}

打印结果如下:

1
2
3
4
5
6
7
8
9
10
[GC (Allocation Failure) [DefNew: 2981K->980K(9216K), 0.0017746 secs] 2981K->980K(19456K), 0.0018187 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
def new generation total 9216K, used 8394K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 90% used [0x00000000fec00000, 0x00000000ff33d8c0, 0x00000000ff400000)
from space 1024K, 95% used [0x00000000ff500000, 0x00000000ff5f5228, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3274K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 354K, capacity 388K, committed 512K, reserved 1048576K

从结果第一行来看,JVM发生了一次MinorGC,跟第一次的结果有明显的变化。

依次放入不同大小的byte[],逐步分析…

3.3 大对象直接晋升老年代

1
2
3
4
5
6
7
8
9
10
11
12
13
//参数: -Xms20m -Xmx20m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
public class TestDemo04 {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 1 * 1024 * 1024;
private static final int _6MB = 6 * 1024 * 1024;
private static final int _7MB = 7 * 1024 * 1024;
private static final int _8MB = 8 * 1024 * 1024;

public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
list.add(new byte[_8MB]);
}
}

打印结果:

1
2
3
4
5
6
7
8
9
Heap
def new generation total 9216K, used 3145K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 38% used [0x00000000fec00000, 0x00000000fef127b8, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000)
Metaspace used 3274K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 354K, capacity 388K, committed 512K, reserved 1048576K

4.垃圾回收器

4.1 串行

  1. 单线程
  2. 堆内存较小,适合个人电脑

JVM-day02-09.png

-XX:+UseSerialGC=Serial(新生代 复制算法) + SerialOld(老年代 标记整理算法)

4.2 吞吐量优先

  1. 多线程
  2. 堆内存较大,多核CPU
  3. 让单位时间内,STW的时间最短 0.2 0.2 = 0.4 垃圾回收时间占比最低,这样就称为吞吐量高

JVM-day02-10.png

-XX:+UseParallelGC ~ -XX:+UseParallelOlgGC
-XX:+UseAdaptiveSizePolicy (动态调整Eden和survivor的比例)
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n

4.3 响应时间优先

  1. 多线程
  2. 堆内存较大,多核CPU
  3. 尽可能让单次STW的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5

JVM-day02-11.png

-XX:+UseConcMarkSweepGC(CMS  concurrent 并发) ~ -XX:+UseParNewGC(新生代) ~ SerialOld 
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads(CPU核数的1/4)
-XX:CMSInitiatingOccupancyFraction=percent(默认65%)
-XX:+CMSScavengeBeforeRemark

4.4 G1

定义:Garbage First

2004 论文发布
2009 JDK 6u14体验
2012 JDK 7u4官方支持
2017 JDK 9 默认

适用场景:

  1. 同时注重吞吐量(Throughput)和低延迟(Low Latency),默认的暂停目标是200ms
  2. 超大堆内存,会将堆划分为多个大小相等的Region
  3. 整体上是 标记+整理 算法,两个区域之间是 复制 算法

相关JVM参数:

-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time

4.4.1 G1垃圾回收阶段

JVM-day02-12.png

4.4.2 Young Collection

会发生STW

JVM-day02-13.png

JVM-day02-14.png

JVM-day02-15.png

4.4.3 Young Collection + CM

  1. 在Young GC时会进行GC Root 的初始标记

  2. 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由下面的JVM参数决定

    -XX:InitiatingHeapOccupancyPercent=percent(默认45%)

JVM-day02-16.png

4.4.4 Mixed Collection 混合回收

会对Eden,Survivor,Old进行全面垃圾回收

  1. 最终标记(Remark)会STW

  2. 拷贝存活(Evacuation)会STW

    -XX:MaxGCPauseMillis=ms

JVM-day02-17.png

4.4.5 Full GC

  1. SerialGC

    (1)新生代内存不足发生的垃圾回收 - MinorGC

    (2)老年代内存不足发生的垃圾回收 - FullGC

  2. ParallelGC

    (1)新生代内存不足发生的垃圾回收 - MinorGC

    (2)老年代内存不足发生的垃圾回收 - FullGC

  3. CMS

    (1)新生代内存不足发生的垃圾回收 - MinorGC

    (2)老年代内存不足

  4. G1

    (1)新生代内存不足发生的垃圾回收 - MinorGC

    (2)老年代内存不足(根据阈值判断)

4.4.6 Young Collection 跨代引用

新生代回收的跨代引用(老年代引用新生代)问题

JVM-day02-18.png

  1. 卡表与Remembered Set
  2. 在引用变更时通过post-write barrier + dirty card queue
  3. concurrent refinement threads 更新 Remembered Set

JVM-day02-19.png

4.4.7 Remark

pre-write barrier(写屏障) + satb_mark_queue

JVM-day02-20.png

4.4.8 JDK 8u20 字符串去重

优点:节省大量内存

缺点:略微多占用了CPU时间,新生代回收时间略微增加

-XX:+UseStringDeduplication

如下:

String s1 = new String("hello");//char[]{'h','e','l','l','0'}
String s2 = new String("hello");//char[]{'h','e','l','l','0'}
  1. 将所有新分配的字符串放入一个队列

  2. 当新生代回收时,G1并发检查是否有字符串重复

  3. 如果他们值一样,让他们引用同一个char[]

  4. 注意:与String.intern()不一样

    (1)String.intern()关注的是字符串对象

    (2)而字符串去重关注的是char[]

    (3)在JVM内部,使用了不同的字符串表

4.4.9 JDK 8u40 并发标记类卸载

所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类

-XX:+ClassUnloadingWithConcurrentMark 默认启用

4.4.10 JDK 8u60 回收巨型对象

  1. 一个对象大于region的一半时,称之为巨型对象
  2. G1不会对巨型对象进行拷贝
  3. 回收时被优先考虑
  4. G1会跟踪老年代所有incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时被处理掉

4.4.11 JDK 9 并发标记起始时间的调整(动态调整阈值)

  1. 并发标记必须在堆空间占满前完成,否则退化为FullGC

  2. JDK9 之前需要使用 -XX:InitiatingHeapOccupancyPercent

  3. JDK9 可以动态调整

    (1)-XX:InitiatingHeapOccupancyPercent 用来设置初始值

    (2)进行数据采样并动态调整

    (3)总会添加一个安全的空档空间

4.4.12 JDK 9 更高效的回收

  1. 250+增强
  2. 180+bug修复

参考资料:

JDK12

JDK15

5.垃圾回收调优

  1. 掌握GC相关的JVM参数参考Oracle官网介绍,会基本的空间调整
  2. 掌握相关工具
  3. 调优跟应用、环境有关,没有放之四海而皆准的法则

5.1 调优领域

  1. 内存
  2. 锁竞争
  3. CPU占用
  4. IO

5.2 确定目标

【低延迟】还是【高吞吐量】,选择合适的回收器

  1. CMS, G1, ZGC
  2. ParallelGC
  3. Zing

5.3 最快的GC 是不发生GC

查看FullGC前后的内存占用,考虑下面几个问题:

  1. 数据是不是太多?

    resultSet = statement.executeQuery(“select * from 大表 limit n”);

  2. 数据表示是否太臃肿?

    (1)对象图

    (2)对象大小 Object(16Byte) Integer(24Byte 包装类型) int(4Byte)

  3. 是否存在内存泄露?

    (1)static Map map

    (2)软

    (3)弱

    (4)第三方缓存实现

5.4 新生代调优

新生代特点

  1. 所有的new操作的内存分配非常廉价

    TLAB thread-local allocation buffer

  2. 死亡对象的回收代价是零

  3. 大部分对象用过即销毁

  4. MinorGC的时间远远低于FullGC

问题:越大越好吗?

-Xmn的官方解释:参考链接

Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). Append the letter k or K to indicate kilobytes, m or M to indicate megabytes, or g or G to indicate gigabytes. The young generation region of the heap is used for new objects. GC is performed in this region more often than in other regions. If the size for the young generation is too small, then a lot of minor garbage collections are performed. If the size is too large, then only full garbage collections are performed, which can take a long time to complete. Oracle recommends that you keep the size for the young generation greater than 25% and less than 50% of the overall heap size. 
  1. 新生代能容纳所有【并发量*(请求-响应)】的数据

  2. 幸存区大到能保留【当前活跃对象+需要晋升对象】

  3. 晋升阈值配置得当,让长时间存活对象尽快晋升

    -XX:MaxTenuringThreshold=threshold

    -XX:+PrintTenuringDistribution

5.5 老年代调优

以CMS为例:

  1. CMS的老年代内存越大越好

  2. 先尝试不做调优,如果没有FullGC则无需调优,否则先尝试调优新生代

  3. 观察发生FullGC时老年代内存占用,将老年代内存预设调大1/4~1/3

    -XX:CMSInitiatingOccupancyFraction=percent

    Twitter工程师建议设置为0,表示老年代一旦有垃圾就回收,但是这种极端做法对CPU的要求特别高。

    一般设置75%到80%之间。

5.6 案例

  1. 案例1:FullGC和MinorGC频繁

    增大新生代的内存,gc频次相应降低

  2. 案例2:请求高峰期发生FullGC,单次暂停时间特别长(CMS)

  3. 案例3:老年代充裕情况下,发生FullGC(CMS jdk7)

    jdk7 堆中的永久代空间不足 导致FullGC

评论加载中