目录
1. 内存监控
参考:《JVM学习-内存监控(五)》
2. GC 性能衡量指标
2.1. 吞吐量
这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算 GC 的吞吐量:系统总运行时间 = 应用程序耗时 +GC 耗时。如果系统运行了 100 分,GC 耗时 1 分钟,则系统吞吐量为 99%。GC 的吞吐量一般不能低于 95%。
2.2. 停顿时间
指垃圾收集器正在运行时,应用程序的暂停时间。对于串行回收器而言,停顿时间可能会比较长;而使用并发回收器,由于垃圾收集器和应用程序交替运行,程序的停顿时间就会变短,但其效率很可能不如独占垃圾收集器,系统的吞吐量也很可能会降低
2.3. 垃圾回收频率
多久发生一次指垃圾回收呢?通常垃圾回收的频率越低越好,增大堆内存空间可以有效降低垃圾回收发生的频率,但同时也意味着堆积的回收对象越多,最终也会增加回收时的停顿时间。所以我们只要适当地增大堆内存空间,保证正常的垃圾回收频率即可。
3. GC 调优策略
3.1 降低 Minor GC 频率
通常情况下,由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC,因此我们可以通过增大新生代空间来降低 Minor GC 的频率。
但是可能有这样的以为,增加Eden区的大小,可以降低Minor GC的次数但是会增加每次Minor的时间
通常Minor的回收步骤是:T1回收Eden区垃圾对象 T2复制存活对象
例如:一个对象的存活时间500ms minorGC间隔为300ms 那么这个对象将被复制
如果增加了eden区大小 minorGC 间隔到500ms 那么这个对象在eden区就会被回收 而不会被复制
复制消耗时间远远大于扫描时间 所以可以适量增加eden区大小
3.2 降低 Full GC 的频率
由于堆内存空间不足或老年代对象太多,会触发 Full GC,频繁的 Full GC 会带来上下文切换,增加系统的性能开销。我们可以使用哪些方法来降低 Full GC 的频率呢?
1.减少创建大对象
在平常的业务场景中,我们习惯一次性从数据库中查询出一个大对象用于 web 端显示。例如,我之前碰到过一个一次性查询出 60 个字段的业务操作,这种大对象如果超过年轻代最大对象阈值,会被直接创建在老年代;即使被创建在了年轻代,由于年轻代的内存空间有限,通过 Minor GC 之后也会进入到老年代。这种大对象很容易产生较多的 Full GC。我们可以将这种大对象拆解出来,首次只查询一些比较重要的字段,如果还需要其它字段辅助查看,再通过第二次查询显示剩余的字段。
2.增大堆内存空间
在堆内存不足的情况下,增大堆内存空间,且设置初始化堆内存为最大堆内存,也可以降低 Full GC 的频率。
3.3. 选择合适的 GC 回收器
假设我们有这样一个需求,要求每次操作的响应时间必须在 500ms 以内。这个时候我们一般会选择响应速度较快的 GC 回收器,CMS(Concurrent Mark Sweep)回收器和 G1 回收器都是不错的选择。而当我们的需求对系统吞吐量有要求时,就可以选择 Parallel Scavenge 回收器来提高系统的吞吐量。
4. 对象在堆中的生命周期
具体详见:《内存模型》
-
new 创建一个对象会优先分配到新生代的eden区 这时虚拟机会给对象定义一个对象年龄计数器(通过参数 -XX:MaxTenuringThreshold 设置)
-
当eden区空间不足会触发一次minor GC 这时候存活对象转移到Survivor 同时年龄计数器+1,在 Survivor 中同样也会经历 MinorGC,每经过一次 MinorGC,对象的年龄将会 +1。
当然了,内存空间也是有设置阈值的,可以通过参数 -XX:PetenureSizeThreshold 设置直接被分配到老年代的最大对象,这时如果分配的对象超过了设置的阀值,对象就会直接被分配到老年代,这样做的好处就是可以减少新生代的垃圾回收。
5. 查看jvm默认配置
其中InitialHeapSize为最开始的堆的大小,MaxHeapSize为堆的最大值。
java -XX:+PrintFlagsFinal -version | grep HeapSize
6. 查看程序jvm配置
《JVM学习-内存监控(五)》
7. 具体调优方法
建议:配置jvm打印日志参数 通过工具分析几个指标
- 现模拟一个抢购接口,假设需要满足一个 高峰值5W 用户的正常处理,且每次请求会产生 20KB 对象,我们可以通过千级并发创建一个 1MB 对象的接口来模拟万级并发请求产生大量对象的场景,具体代码如下:
@RequestMapping("/testController")
@Controller
public class TestController {
/**
* jvm参数配置: -Xmx500m -Xms500m -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/Users/liqiang/Desktop/logs/gc.log
* ab -c 10 -n 100 localhost:8999/testController/test2
* @param request
* @return
*/
@RequestMapping(value = "/test2")
public String test1(HttpServletRequest request) {
List temp = new ArrayList();
Byte[] b = new Byte[1024 * 1024];
temp.add(b);
return "success";
}
}
- 我们压测并发请求取5000 参考《压测工具-ab 如何获取UA和TPS》
可以发现平均响应在7秒 同时吞吐率也没上去
使用gcviewer分析日志:参考《JVM学习-内存监控(五) GCviewer》
可以发现有34次fullgc fullgc会造成 stop-work-allstop-the-world gc性能参考指标:点击跳转
同时内存整个gc内存使用高达97%以及 老年代高达99%
3. 调整jvm内存
-Xmx2g -Xms2g -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/Users/liqiang/Desktop/logs/gc.log
压测发现 吞吐率上去了 平均响应时间也 减少了 但是响应时间还是有点长
可以发现 没有fullgc了 那个2不用看 每次启动都会 有 同时总的内存是42% 堆是18% 因为有48次minnor gc 但是 老年代 闲置又比较多 我们尝试 提高年轻代的内存 使eden区内存更大 减少 回收次数
3.提高年轻代内存 减少老年代内存
可以发现年轻代回收略微减少 吞吐量 略微提交
当然这么分配是 不合理的(建议按官方的 1:2的比例分配 不要 轻易修改)。因为我们测试数据都是存活时间的很短的,这里只是为了测试 实际中灵活调整
-Xmn1500m -Xmx2g -Xms2g -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/Users/liqiang/Desktop/logs/gc.log