二、垃圾回收

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

1.1引用计数法

  • 有引用+1
  • 断开引用-1
  • 弊端:循环依赖问题
  • java未使用该方法

1.2 可达性分析计算法

  • Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象

  • 扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以 回收

  • 哪些对象可以作为 GC Root ?

  • 类比葡萄串🍇,提起葡萄还在根上的就不能被回收
    案例:

  • 保存jmap状态

1
2
3
4
5
6
7
8
9
10
11
❯ jps
31112 Main
34184 Jps
33769 Launcher
33770 Demo2_2
❯ jmap -dump:format=b,live,file=1.bin 33770
Dumping heap to /Users/cyt/workspace/java/jvm-std/jvm/src/cn/itcast/jvm/t2/1.bin ...
Heap dump file created
❯ jmap -dump:format=b,live,file=2.bin 33770
Dumping heap to /Users/cyt/workspace/java/jvm-std/jvm/src/cn/itcast/jvm/t2/2.bin ...
Heap dump file created
  • mat 软件打开分析
  • GC ROOT

1.3 四种引用

  1. 强引用
    • 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
  2. 软引用(SoftReference)
    • 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用 对象
    • 可以配合引用队列来释放软引用自身
  3. 弱引用(WeakReference)
    • 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
    • 可以配合引用队列来释放弱引用自身
  4. 虚引用(PhantomReference)
    • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队, 由 Reference Handler 线程调用虚引用相关方法释放直接内存
  5. 终结器引用(FinalReference)
    • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象

强软弱

  • 实线是强引用
  • 软 弱引用断开
    • 内存不够回收软引用
    • 无论充足与否都回收弱引用
    • 然后放入 引用队列 (可以使用)

虚终

  • 必须使用引用队列
  • 虚引用,放入引用队列,线程扫描队列主动释放 直接内存
    • |650
  • 终结引用
    • 每个类继承自Object类,有一个finallize 方法 (不推荐)
    • 放入引用队列
    • 由finalizeHandler扫描释放

软引用

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
/**  
* 演示软引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/public class Demo2_3 {
private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) throws IOException {
// List<byte[]> list = new ArrayList<>();
// for (int i = 0; i < 5; i++) {
// list.add(new byte[_4MB]);
// }
//
// System.in.read();
soft();
}
public static void soft() {
// list --> SoftReference --> byte[]

List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
System.out.println("循环结束:" + list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
}
  • 使用普通方法分配内存 出现内存溢出
  • 使用软引用结果:
  • 观察到有四个null
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
[B@4554617c
1
[B@74a14482
2
[B@1540e19d
3
[B@677327b6
4
[GC (Allocation Failure) --[PSYoungGen: 5678K->5678K(7168K)] 17966K->17974K(23552K), 0.0006355 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 5678K->4453K(7168K)] [ParOldGen: 12296K->12288K(16384K)] 17974K->16741K(23552K), [Metaspace: 3286K->3286K(1056768K)], 0.0018124 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 4453K->4453K(7168K)] 16741K->16749K(23552K), 0.0004038 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4453K->0K(7168K)] [ParOldGen: 12296K->331K(10240K)] 16749K->331K(17408K), [Metaspace: 3286K->3286K(1056768K)], 0.0014106 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[B@14ae5a5
5
循环结束:5
null
null
null
null
[B@14ae5a5
Heap
PSYoungGen total 7168K, used 4343K [0x00000007bf800000, 0x00000007c0000000, 0x00000007c0000000)
eden space 6144K, 70% used [0x00000007bf800000,0x00000007bfc3de00,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
ParOldGen total 10240K, used 331K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf800000)
object space 10240K, 3% used [0x00000007be800000,0x00000007be852e20,0x00000007bf200000)
Metaspace used 3313K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K

软引用+引用队列

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
/**  
* 演示软引用, 配合引用队列
*/
public class Demo2_4 {
private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) {
List<SoftReference<byte[]>> list = new ArrayList<>();

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

for (int i = 0; i < 5; i++) {
// 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
System.out.println(ref.get());
list.add(ref);
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[]> reference : list) {
System.out.println(reference.get());
}
}
}
  • 结果:
  • 四个null被回收
1
2
3
4
5
6
7
8
9
10
11
12
[B@4554617c
1
[B@74a14482
2
[B@1540e19d
3
[B@677327b6
4
[B@14ae5a5
5
===========================
[B@14ae5a5

弱引用

  • 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**  
* 演示弱引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/public class Demo2_5 {
private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) {
// list --> WeakReference --> byte[]
List<WeakReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
list.add(ref);
for (WeakReference<byte[]> w : list) {
System.out.print(w.get()+" ");
}
System.out.println();

}
System.out.println("循环结束:" + list.size());
}
}
  • 结果:
  • null [B@74a14482 [B@1540e19d [B@677327b6 null null null null null [B@330bedb4 null为回收的数组对象
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
[B@4554617c 
[B@4554617c [B@74a14482
[B@4554617c [B@74a14482 [B@1540e19d
[B@4554617c [B@74a14482 [B@1540e19d [B@677327b6
[GC (Allocation Failure) [PSYoungGen: 5678K->528K(7168K)] 17966K->12824K(23552K), 0.0006993 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null [B@74a14482 [B@1540e19d [B@677327b6 [B@14ae5a5 // 第五次 放不下 回收第一个
[GC (Allocation Failure) [PSYoungGen: 4747K->496K(7168K)] 17043K->12792K(23552K), 0.0004962 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null [B@74a14482 [B@1540e19d [B@677327b6 null [B@7f31245a
[GC (Allocation Failure) [PSYoungGen: 4776K->448K(7168K)] 17072K->12744K(23552K), 0.0005593 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null [B@74a14482 [B@1540e19d [B@677327b6 null null [B@6d6f6e28
[GC (Allocation Failure) [PSYoungGen: 4705K->432K(7168K)] 17001K->12728K(23552K), 0.0004255 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null [B@74a14482 [B@1540e19d [B@677327b6 null null null [B@135fbaa4
[GC (Allocation Failure) [PSYoungGen: 4823K->496K(7168K)] 17119K->12800K(23552K), 0.0005512 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
null [B@74a14482 [B@1540e19d [B@677327b6 null null null null [B@45ee12a7
[GC (Allocation Failure) [PSYoungGen: 4727K->512K(7168K)] 17031K->12816K(23552K), 0.0003853 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null [B@74a14482 [B@1540e19d [B@677327b6 null null null null null [B@330bedb4
循环结束:10
Heap
PSYoungGen total 7168K, used 4799K [0x00000007bf800000, 0x00000007c0000000, 0x00000007c0000000)
eden space 6144K, 69% used [0x00000007bf800000,0x00000007bfc2fef0,0x00000007bfe00000)
from space 1024K, 50% used [0x00000007bff00000,0x00000007bff80000,0x00000007c0000000)
to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
ParOldGen total 16384K, used 12304K [0x00000007be800000, 0x00000007bf800000, 0x00000007bf800000)
object space 16384K, 75% used [0x00000007be800000,0x00000007bf404030,0x00000007bf800000)
Metaspace used 3363K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 361K, capacity 388K, committed 512K, reserved 1048576K

2. 垃圾回收算法

2.1 标记清除法

  • 定义: Mark Sweep
  • 标记:没被 GC Root直接引用的对象标记为垃圾(灰色)
  • 优缺点
    • 速度较快
    • 会造成内存碎片
      |500

2.2 标记整理

  • 定义:Mark Compact
  • 清除之后 紧凑操作 (类似OS)
    • 速度慢
    • 没有内存碎片

2.3 复制

  • 定义:Copy

    • 不会有内存碎片
    • 需要占用双倍内存空间
  • 标记

  • 复制移动

  • 全部清除左边

  • 交换

2.4 总结

  • 以上三种都会用到,接下来介绍分代垃圾回收机制

3. 分代垃圾回收

  • 对象首先分配在伊甸园区域

  • 新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制幸存区to 中,存活的 对象年龄加 1并且交换 from和to

  • 第二次 minor gc

  • minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行

  • 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)

  • 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时 间更长、

  • 理解

    • 新生代类似日常垃圾,每个居民放在垃圾桶
    • 老年代类似破椅子,破电视,先暂存家里,定时大清理

3.1 相关VM参数

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

3.2 GC演示

  • 新生代 -Xmn10M 10mb

  • 老年代: 10mb

  • 新生代默认1mb from; 1mb to; eden 8mb

  • 放入7mb,触发GC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**  
* 演示内存的分配策略
*/
public class Demo2_1 {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 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;

// -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
public static void main(String[] args) throws InterruptedException {
ArrayList<byte[]> list = new ArrayList<>();
list.add(new byte[_7MB]);
}
}
  • minor GC 默认写 GC

  • 大对象回收

    • 放入8mb,新生代放不下,直接晋升老年代
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**  
* 演示内存的分配策略
*/
public class Demo2_1 {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 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;

// -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
public static void main(String[] args) throws InterruptedException {
ArrayList<byte[]> list = new ArrayList<>();
list.add(new byte[_8MB]);
}
}

  • OOM
    • 子线程内存溢出,不会导致主线程终止
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
/**  
* 演示内存的分配策略
*/
public class Demo2_1 {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 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;

// -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
public static void main(String[] args) throws InterruptedException {
// ArrayList<byte[]> list = new ArrayList<>();
// list.add(new byte[_8MB]);
// list.add(new byte[_8MB]);

// 线程演示
new Thread(() -> {
ArrayList<byte[]> list = new ArrayList<>();
list.add(new byte[_8MB]);
list.add(new byte[_8MB]);
}).start();

System.out.println("sleep....");
Thread.sleep(100000000L);
}
}

4. 垃圾回收器

  • 串行
    • 单线程
    • 堆内存较小,适合个人电脑
  • 吞吐量优先
    • 多线程
    • 堆内存较大,多核 cpu
    • 让单位时间内,STW 的时间最短(总共时间最短) 0.2+0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高
  • 响应时间优先
    • 多线程
    • 堆内存较大,多核 cpu
    • 尽可能让单次 STW 的时间最短 (单词时间最短)0.1 0.1 0.1 0.1 0.1 = 0.5

4.1 串行

  • 使用:-XX:+UseSerialGC = Serial + SerialOld
    • Serial 使用复制算法
    • SerialOld 老年代
  • 回收过程
    • cpu0 1 2 3 都在运行
    • 发生垃圾回收时,都在安全点停下来
    • 垃圾回收线程运行,其他线程阻塞
    • 回收后 其他线程运行

4.2 吞吐量优先

  • -XX:+UseParallelGC ~ -XX:+UseParallelOldGC

    • 开启一个自动开启另一个
  • -XX:+UseAdaptiveSizePolicy

    • 采用自适应大小策略
    • 新生代大小:Eden和幸存区, 晋升阈值
  • -XX:GCTimeRatio=ratio

    • 垃圾回收时间和总时间占比 (1/(1+ratio))
  • -XX:MaxGCPauseMillis=ms

    • 最大暂停毫秒数
    • 默认 200ms
    • 和上一个配置矛盾,折中
  • -XX:ParallelGCThreads=n

    • 控制线程数
  • 不同点:

    • 安全点后:开启多个垃圾回收线程

4.3 响应时间优先

  • -XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
    • 解释:concurrent(并发的) mark(标记)sweep(清除)
    • CMS(Concurrent Mark-Sweep) 并发标记清除回收机制
    • (区别并发与并行)并发:交替进行;并行:同时发生
    • UseConcMarkSweepGC:工作在老年代
    • UseParNewGC:工作在新生代
    • 新老有碎片,内存都不足时,退化为 SerialOld 做串行回收
  • -XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
    • 设置并发数:一般是占1/4
  • -XX:CMSInitiatingOccupancyFraction=percent
    • cms执行垃圾回收时的占比
    • percent百分比越小,执行越早
    • 早期cms 65%左右
  • -XX:+CMSScavengeBeforeRemark
    • 重新标记:新生代可能用到老年代,需要重新扫描一遍
      |1550

4.1 G1 (重点)

定义:Garbage First

  • 2004 论文发布

  • 2009 JDK 6u14 体验

  • 2012 JDK 7u4 官方支持

  • 2017 JDK 9 默认

  • 适用场景

    • 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
    • 适用超大堆内存,会将堆划分为多个大小相等的 Region
    • 整体上是 标记+整理 算法,两个区域(Region)之间是 复制 算法
      相关 JVM 参数
  • -XX:+UseG1GC

    • JDK8 不默认,需要手动开启
    • JDK9 及以后自动开启
  • -XX:G1HeapRegionSize=size

    • 设置区域大小 (1 2 4 8 16)
  • -XX:MaxGCPauseMillis=time

1)G1垃圾回收阶段

  • 新生代垃圾收集
  • 新生代垃圾收集+并发标记
  • 混合收集
  • 将内存分为很多区域:Region,每个区域都可作为eden 幸存区 老年代

2)Young Collection(新生代回收)

  • 会 STW
  • 此时其他线程阻塞,只有垃圾回收线程运行
  • E:eden 伊甸园
    |475
  • 新生区:eden复制copy到 S(幸存区Servival)
  • 幸存区晋升到老年代(O)
    • 部分可复制到新的幸存区

3)Young Collection + CM(并发标记)

  • 在 Young GC 时会进行 GC Root 的初始标记
  • 老年代占用堆空间比例达到阈值时,进行并发标记CM(不会 STW),由下面的 JVM 参数决定
  • -XX:InitiatingHeapOccupancyPercent=percent (默认45%)
    |575

4)Mixed Collection

会对 E、S、O 进行全面垃圾回收

  • 最终标记(Remark)会 STW

  • 拷贝存活(Evacuation)会 STW

  • -XX:MaxGCPauseMillis=ms

  • 部分老年代会回收到 新的老年代区域

    • 根据暂停时间
    • 回收最多的 老年代 copy算法
  • 新生代未达到阈值放入 新的新生代

    • 达到阈值的放入 老年代
      |550

5) Full GC

  • SerialGC
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • CMS
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
  • G1
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
      • CM + 混合
      • 回收速度跟不上:full gc

6) Young Collection 跨代引用

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

  • 对老年代划分为多个 512kb

    • 弱引用了eden, 则标记为 脏卡
    • 每次GC Root扫描则只管脏卡
      |625
  • 卡表与 Remembered Set

    • 新生代有 Remembered Set,记录哪些引用了自己
  • 在引用变更时通过 post-write barrier + dirty card queue

    • 开线程异步更改卡表
  • concurrent refinement threads 更新 Remembered Set
    |650

7) Remark(重新标记)

  • pre-write barrier + satb_mark_queue 写屏障+队列

    • 黑色:处理完 (不是垃圾)
    • 灰色:处理中
    • 白色:没处理 (垃圾)
  • remark
    |350

  • 加入队列
    |500

8) JDK 8u20 字符串去重

  • 优点:节省大量内存
  • 缺点:略微多占用了 cpu 时间,新生代回收时间略微增加
  • -XX:+UseStringDeduplicatio
1
2
3
String s1 = new String("hello"); // char[{'h','e','l','l','o'} 

String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
  • 将所有新分配的字符串放入一个队列
  • 当新生代回收时,G1并发检查是否有字符串重复
  • 如果它们值一样,让它们引用同一个 char[]
  • 注意,与 String.intern() 不一样
    • String.intern() 关注的是字符串对象
    • 而字符串去重关注的是 char[]
    • 在JVM 内部,使用了不同的字符串表

9) JDK 8u40 并发标记类卸载

  • 所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸 载它所加载的所有类
  • -XX:+ClassUnloadingWithConcurrentMark 默认启用

10) JDK 8u60 回收巨型对象

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

11) JDK 9 并发标记起始时间的调整

  • 并发标记必须在堆空间占满前完成,否则退化为 FullGC
  • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent
  • JDK 9 可以动态调整
    • -XX:InitiatingHeapOccupancyPercent 用来设置初始值
    • 进行数据采样并动态调整
    • 总会添加一个安全的空档空间

12) JDK 9 更高效的回收

5. 垃圾回收调优

  • 预备知识
    • 掌握 GC 相关的 VM 参数,会基本的空间调整
    • 掌握相关工具
    • 明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则

5.1 调优领域

  • 内存
  • 锁竞争
  • cpu
  • 占用 io

5.2 确定目标

  • 【低延迟】还是【高吞吐量】,选择合适的回收器
  • CMS,G1,ZGC (低延迟)
  • ParallelGC (高吞吐)
  • Zing

5.3 最快的 GC是不发生 GC

  • 查看 FullGC 前后的内存占用,考虑下面几个问题
    • 数据是不是太多?
      • resultSet = statement.executeQuery(“select * from 大表 limit n”)
      • select * from 大表 加载所有数据到内存
    • 数据表示是否太臃肿?
      • 对象图
      • 对象大小 16; Integer 24; int 4
    • 是否存在内存泄漏?
      • static Map map =
      • 第三方缓存实现

5.4 新生代调优

  • 新生代的特点

    • 所有的 new 操作的内存分配非常廉价
      • TLAB thread-local allocation buffer 线程局部的
    • 死亡对象的回收代价是零 (死亡对象内存为0, 复制算法代价0)
    • 大部分对象用过即死
    • Minor GC 的时间远远低于 Full GC
  • 越大越好吗?
    -Xmn

  • Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). 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

  • 设置年轻代( nursery)的初始和最大大小(以字节为单位)。垃圾回收在这个区域比在其他区域进行得更频繁。如果年轻代的大小太小,就会进行很多次 Minor GC。如果大小太大,就只会进行 Full GC,这可能会花费很长时间完成。Oracle 建议年轻代的大小保持在总堆大小的 25% 到 50% 之间。

  • 新生代能容纳所有【并发量 * (请求-响应)】的数据

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

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

  • -XX:MaxTenuringThreshold=threshold 晋升阈值

  • -XX:+PrintTenuringDistribution

1
2
3
4
5
Desired survivor size 48286924 bytes, new threshold 10 (max 10) 
- age 1: 28992024 bytes, 28992024 total
- age 2: 1366864 bytes, 30358888 total
- age 3: 1425912 bytes, 31784800 total
...

5.5 老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
    • -XX:CMSInitiatingOccupancyFraction=percent
    • 老年代占比percent时发生回收

5.6 案例

  • 案例1: Full GC 和 Minor GC频繁
    • 适当增大新生代
    • 增大晋升阈值
  • 案例2 请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)
    • 查看GC日志
    • 查看哪个阶段时间长(初始标记,并发标记,重新标记,并发清理)
    • 一般是重新标记时间长
    • 使用 -XX:+CMSScavengeBeforeRemark 重新标记前清理新生代
  • 案例3 老年代充裕情况下,发生 Full GC (CMS jdk1.7)