JVM调优 内存调优实践

JVM内存调优-实践测试

    • 一、测试案例
    • 二、测试过程
      • 1. 10并发线程/50000请求量 结果(吞吐量:531 平均响应时间:17)
      • 2.20并发线程/50000请求量 结果(吞吐量:566平均响应时间:39)
      • 3.40并发线程/50000请求量 结果(吞吐量:530平均响应时间:78)
      • 4.50并发线程/50000请求量 结果(吞吐量:516平均响应时间:90)
    • 三、优化方案
      • 1.增大堆内存,减少FULL GC 频率
      • 2.增大年轻代,减少YGC 频率
      • 3.我们同时调大堆内存和年轻代
      • 4.设置 Eden、Survivor 区比例
      • 5.根据需求,设置垃圾回收器
      • 6.调整各种阈值参数
      • 总结
    • 四、JVM相关知识
      • 1.JVM内存相关配置参数
      • 2.GC垃圾回收器
      • 3.GC性能衡量指标
      • 4.年轻代对象进入老年代条件
      • 5.常用工具以及排查命令
      • 6.常用内存异常
    • 五、JVM系列文章

这篇文章通过一个简单的案例来向大家介绍一下几种JVM内存调优方式,希望大家可以通过实践将理论知识形成一个闭环,纸上谈兵是最没有意义的

一、测试案例

该接口 随机产生不同大小的对象,最大4M

/**
     * 内存调优测试
     * @return
     */
    @GetMapping("/tuningTest")
    public String tuningTest(){
        List temp = new ArrayList();
        // 随机产生不同大小的对象,最大4MB
        int i = new Random().nextInt(8)+1;
        Byte[] bytes = new Byte[1024 * 512 * i];
        temp.add(bytes);
        return "Success";
    }

二、测试过程

首先最大堆内存设置800M,初始堆大小也是800M,测试不同并发线程相同请求量的吞吐量以及相应时间

1. 10并发线程/50000请求量 结果(吞吐量:531 平均响应时间:17)

JVM调优 内存调优实践_第1张图片
JVM调优 内存调优实践_第2张图片

2.20并发线程/50000请求量 结果(吞吐量:566平均响应时间:39)

JVM调优 内存调优实践_第3张图片

JVM调优 内存调优实践_第4张图片

3.40并发线程/50000请求量 结果(吞吐量:530平均响应时间:78)

JVM调优 内存调优实践_第5张图片
JVM调优 内存调优实践_第6张图片

4.50并发线程/50000请求量 结果(吞吐量:516平均响应时间:90)

JVM调优 内存调优实践_第7张图片

JVM调优 内存调优实践_第8张图片

从上述结果我们可以看到随着并发数量的上升,吞吐量很快就上不去了,而且响应时间却在不断上升,我们以第三次结果来看看GC 情况 (多次测试结果不一定一样,因为接口产生的对象大小随机)

YGC : 3481次 平均时间:73.9ms FGC :70次 平均时间:3.3ms

JVM调优 内存调优实践_第9张图片

三、优化方案

1.增大堆内存,减少FULL GC 频率

(我们把堆内存设置到1.2G,以上述第三种情况测试,因为上述第三种吞吐量上不去了)

结果:(吞吐量:602 平均响应时间:79 FULL GC次数:28 )

可以看到虽然响应时间没变但吞吐量上去了,而且FULL GC 次数大大减小了

JVM调优 内存调优实践_第10张图片

JVM调优 内存调优实践_第11张图片

JVM调优 内存调优实践_第12张图片

2.增大年轻代,减少YGC 频率

(堆内存800M不变,年轻代设置到600M)

结果:(吞吐量:914平均响应时间:49 YGC 次数:1828 49ms FULL GC次数:28 )

虽然FGC次数没变,但吞吐量达到了惊人的914 响应时间也降了下来,YGC次数几乎减少了一半
JVM调优 内存调优实践_第13张图片
JVM调优 内存调优实践_第14张图片

JVM调优 内存调优实践_第15张图片

3.我们同时调大堆内存和年轻代

(堆内存1200M,年轻代900M)

结果:(吞吐量:958平均响应时间:41 YGC 次数:756 15ms FULL GC次数:23)

JVM调优 内存调优实践_第16张图片
JVM调优 内存调优实践_第17张图片

在这里插入图片描述

4.设置 Eden、Survivor 区比例

在 JDK1.8 中,默认是开启 AdaptiveSizePolicy 的,则每次 GC后都会重新计算 Eden、From Survivor 和 To Survivor 区的大小,计算依据是 GC 过程中统计的 GC 时间、吞吐量、内存占用量。我们可以通过 -XX:-UseAdaptiveSizePolicy 关闭该项配置,或显示运行 -XX:SurvivorRatio=8 -XX:NewRatio=2 将 Eden、Survivor 的比例固定。

这个比例的设置情况太多了, 我就不再演示了

5.根据需求,设置垃圾回收器

这个我也就不再演示了因为到了这一步都会基于场景优化了

6.调整各种阈值参数

不演示,这里DEMO情况过于简单,容易产生误导

总结

调优是一个需要基于场景、基于某种指标,大量测试得出来的一种结论,没有什么万金油的配置,实践大于一切,在复杂的业务场景下,你的理论知识终究是纸上谈兵,调优终究还是内存的利用,以及对项目的熟悉,还有业务场景的适配。

了解对象在JVM的存活周期、了解在JVM升代的过程、了解大对象产生的场景、业务场景、并发量、GC频率、GC平均时间等等,都能给你调优带来很大帮助

四、JVM相关知识

1.JVM内存相关配置参数

  • 1. -XX:MaxTenuringThreshold:对象年龄计数器 升代的年龄阈值
  • 2.-XX:PetenureSizeThreshold:直接分配到老年代的对象大小阈值
  • 3. -XX:+UseAdaptiveSizePolicy:是否动态调整JVM各区域大小以及进入老年代的年龄(JDK8默认开启,建议不要随便关闭,除非你对JVM内存有了非常明确的规划)
  • 4.-XX:MaxRAMPercentage : 以容器的大小为准,按比例分配内存给JVM
  • 5. -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/ #{path}: 打印GC LOG文件到#{path}路径
  • 6.-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/#{path} :服务第一次内存溢出的时候打印Dump文件到#{path}路径
  • 7.-Xms:堆初始大小
  • 8.-Xmx:堆最大值
  • 9. -Xmn:年轻代大小
  • 10. -XX:SurvivorRatio 、 -XX:NewRatio :设置Eden、Survivor的比例

2.GC垃圾回收器

回收算法类型
JVM调优 内存调优实践_第18张图片

回收器类型以及设置参数

JVM调优 内存调优实践_第19张图片

3.GC性能衡量指标

  • 吞吐量:这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算 GC 的吞吐量:系统总运行时间 = 应用程序耗时 +GC 耗时。如果系统运行了 100 分钟,GC 耗时 1 分钟,则系统吞吐量为 99%。GC 的吞吐量一般不能低于95%。
  • 停顿时间:指垃圾收集器正在运行时,应用程序的暂停时间。对于串行回收器而言,停顿时间可能会比较长;而使用并发回收器,由于垃圾收集器和应用程序交替运行,程序的停顿时间就会变短,但其效率很可能不如独占垃圾收集器,系统的吞吐量也很可能会降低。
  • 垃圾回收频率:多久发生一次指垃圾回收呢?通常垃圾回收的频率越低越好,增大堆内存空间可以有效降低垃圾回收发生的频率,但同时也意味着堆积的回收对象越多,最终也会增加回收时的停顿时间。所以我们只要适当地增大堆内存空间,保证正常的垃圾回收频率即可。

4.年轻代对象进入老年代条件

1.正常晋升:年龄超过设置计数器的阈值

2.大对象晋升:大小超过设置的阈值

3.动态晋升:GC后,Survivor区域中的几个年龄对象加起来超过了Survivor区内存的一半,年龄大的晋升

4.GC后,存活下来的对象太多,此时有新来对象Survivor区放不下,存活的老对象直接晋升老年代

5.常用工具以及排查命令

  • 1.MAT (Eclipse Memory Analyzer )工具 :分析Dump内存文件
  • 2.GCView工具 :这可以打开GC log文件 便于分析
  • 3.top命令:查看系统CPU 以及 内存
  • 4.jstat 命令
  • 5.jmap命令
  • 6.jstack命令

6.常用内存异常

  • 1. java heap space:堆内存溢出,FGC后依旧内存不够分配,会引起频繁FGC
  • 2.PermGen space:方法区溢出
  • 3.StackOverflowError: 栈溢出,每个请求的线程都有一个分配的栈大小,一般是死循环或者深度递归导致
  • 4. GC overhead limit exceeded:超出GC开销限制,意思是GC占用大量时间释放了很小的空间,一种内存警告,多半要发生堆内存溢出

五、JVM系列文章

  • (一)CPU 100%排查及常见案例
  • (二)内存 100%排查及常见案例
  • (三)JVM内存调优-实践测试

你可能感兴趣的:(jvm,java,面试)