JVM学习-jvm调优(八)

内存监控

参考:《JVM学习-内存监控(五)》

GC 性能衡量指标

吞吐量

这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算 GC 的吞吐量:系统总运行时间 = 应用程序耗时 +GC 耗时。如果系统运行了 100 分,GC 耗时 1 分钟,则系统吞吐量为 99%。GC 的吞吐量一般不能低于 95%。

停顿时间

指垃圾收集器正在运行时,应用程序的暂停时间。对于串行回收器而言,停顿时间可能会比较长;而使用并发回收器,由于垃圾收集器和应用程序交替运行,程序的停顿时间就会变短,但其效率很可能不如独占垃圾收集器,系统的吞吐量也很可能会降低

垃圾回收频率

多久发生一次指垃圾回收呢?通常垃圾回收的频率越低越好,增大堆内存空间可以有效降低垃圾回收发生的频率,但同时也意味着堆积的回收对象越多,最终也会增加回收时的停顿时间。所以我们只要适当地增大堆内存空间,保证正常的垃圾回收频率即可。

GC 调优策略

1. 降低 Minor GC 频率

通常情况下,由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC,因此我们可以通过增大新生代空间来降低 Minor GC 的频率。

但是可能有这样的以为,增加Eden区的大小,可以降低Minor GC的次数但是会增加每次Minor的时间

通常Minor的回收步骤是:T1回收Eden区垃圾对象 T2复制存活对象

例如:一个对象的存活时间500ms  minorGC间隔为300ms  那么这个对象将被复制

       如果增加了eden区大小 minorGC 间隔到500ms  那么这个对象在eden区就会被回收 而不会被复制 

  复制消耗时间远远大于扫描时间  所以可以适量增加eden区大小

2.降低 Full GC 的频率

由于堆内存空间不足或老年代对象太多,会触发 Full GC,频繁的 Full GC 会带来上下文切换,增加系统的性能开销。我们可以使用哪些方法来降低 Full GC 的频率呢?

1.减少创建大对象

在平常的业务场景中,我们习惯一次性从数据库中查询出一个大对象用于 web 端显示。例如,我之前碰到过一个一次性查询出 60 个字段的业务操作,这种大对象如果超过年轻代最大对象阈值,会被直接创建在老年代;即使被创建在了年轻代,由于年轻代的内存空间有限,通过 Minor GC 之后也会进入到老年代。这种大对象很容易产生较多的 Full GC。我们可以将这种大对象拆解出来,首次只查询一些比较重要的字段,如果还需要其它字段辅助查看,再通过第二次查询显示剩余的字段。

2.增大堆内存空间

在堆内存不足的情况下,增大堆内存空间,且设置初始化堆内存为最大堆内存,也可以降低 Full GC 的频率。

3.选择合适的 GC 回收器

假设我们有这样一个需求,要求每次操作的响应时间必须在 500ms 以内。这个时候我们一般会选择响应速度较快的 GC 回收器,CMS(Concurrent Mark Sweep)回收器和 G1 回收器都是不错的选择。而当我们的需求对系统吞吐量有要求时,就可以选择 Parallel Scavenge 回收器来提高系统的吞吐量。

对象在堆中的生命周期

具体详见:《内存模型》

1.new 创建一个对象会优先分配到新生代的eden区 这时虚拟机会给对象定义一个对象年龄计数器(通过参数 -XX:MaxTenuringThreshold 设置)

2.当eden区空间不足会触发一次minor GC 这时候存活对象转移到Survivor 同时年龄计数器+1,在 Survivor 中同样也会经历 MinorGC,每经过一次 MinorGC,对象的年龄将会 +1。

当然了,内存空间也是有设置阈值的,可以通过参数 -XX:PetenureSizeThreshold 设置直接被分配到老年代的最大对象,这时如果分配的对象超过了设置的阀值,对象就会直接被分配到老年代,这样做的好处就是可以减少新生代的垃圾回收。

查看jvm默认配置

其中InitialHeapSize为最开始的堆的大小,MaxHeapSize为堆的最大值。

java -XX:+PrintFlagsFinal -version | grep HeapSize

查看程序jvm配置

JVM学习-内存监控(五)

具体调优方法 

1.现模拟一个抢购接口,假设需要满足一个 高峰值5W 用户的正常处理,且每次请求会产生 20KB 对象,我们可以通过千级并发创建一个 1MB 对象的接口来模拟万级并发请求产生大量对象的场景,具体代码如下

/**
 * @author liqiang
 * @date 2020/1/20 11:44
 * @Description:
 */
@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";
    }

}

2.我们压测并发请求取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

原文地址:https://www.cnblogs.com/LQBlog/p/12911033.html