• JVM成神之路(十二) -- Jvm性能优化指南


    # 性能优化

    JVM的性能优化可以分为代码层面和非代码层面。

    在代码层面,大家可以结合字节码指令进行优化,比如一个循环语句,可以将循环不相关的代码提取到循环体之外,这样在字节码层面就不需要重复执行这些代码了。

    在非代码层面,一般情况可以从内存、gc以及cpu占用率等方面进行优化。

    注意,JVM调优是一个漫长和复杂的过程,而在很多情况下,JVM是不需要优化的,因为JVM本身已经做了很多的内部优化操作。

    那今天我们就从内存、gc以及cpu这3个方面和大家一起探讨一下JVM的优化,但是大家要注意的是不要为了调优和调优

    4.1 内存

    4.1.1 内存分配

    正常情况下不需要设置,那如果是促销或者秒杀的场景呢?

    每台机器配置2c4G,以每秒3000笔订单为例,整个过程持续60秒

    在这里插入图片描述

    4.1.2 内存溢出(OOM)

    一般会有两个原因:

    (1)大并发情况下

    (2)内存泄露导致内存溢出

    4.1.2.1 大并发[秒杀]

    浏览器缓存、本地缓存、验证码

    CDN静态资源服务器

    集群+负载均衡

    动静态资源分离、限流[基于令牌桶、漏桶算法]

    应用级别缓存、接口防刷限流、队列、Tomcat性能优化

    异步消息中间件

    Redis热点数据对象缓存

    分布式锁、数据库锁

    5分钟之内没有支付,取消订单、恢复库存等

    4.1.2.2 内存泄露导致内存溢出

    ThreadLocal引起的内存泄露,最终导致内存溢出

    public class TLController {
     @RequestMapping(value = "/tl")
     public String tl(HttpServletRequest request) {
         ThreadLocal tl = new ThreadLocal();
         // 1MB
         tl.set(new Byte[1024*1024]);
         return "ok";
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (1)上传到阿里云服务器

    jvm-case-0.0.1-SNAPSHOT.jar

    (2)启动

    java -jar -Xms1000M -Xmx1000M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=jvm.hprof  jvm-case-0.0.1-SNAPSHOT.jar
    
    • 1

    (3)使用jmeter模拟10000次并发

    39.100.39.63:8080/tl

    (4)top命令查看

    top
    top -Hp PID
    
    • 1
    • 2

    (5)jstack查看线程情况,发现没有死锁或者IO阻塞的情况

    jstack PID
    java -jar arthas.jar   --->   thread
    
    • 1
    • 2

    (6)查看堆内存的使用,发现堆内存的使用率已经高达88.95%

    jmap -heap PID
    java -jar arthas.jar   --->   dashboard
    
    • 1
    • 2

    (7)此时可以大体判断出来,发生了内存泄露从而导致的内存溢出,那怎么排查呢?

    jmap -histo:live PID | more
    获取到jvm.hprof文件,上传到指定的工具分析,比如heaphero.io
    
    • 1
    • 2

    4.2 GC

    这里以G1垃圾收集器调优为例

    4.2.1 是否选用G1

    官网:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases

    (1)50%以上的堆被存活对象占用
    (2)对象分配和晋升的速度变化非常大
    (3)垃圾回收时间比较长
    
    • 1
    • 2
    • 3

    4.2.2 G1调优

    (1)使用G1GC垃圾收集器: -XX:+UseG1GC

    修改配置参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间

    Throughput       Min Pause       Max Pause      Avg Pause       GC count
      99.16%         0.00016s         0.0137s        0.00559s          12 
    
    • 1
    • 2

    (2)调整内存大小再获取gc日志分析

    -XX:MetaspaceSize=100M
    -Xms300M
    -Xmx300M
    
    • 1
    • 2
    • 3

    比如设置堆内存的大小,获取到gc日志,使用GCViewer分析吞吐量和响应时间

    Throughput       Min Pause       Max Pause      Avg Pause       GC count
      98.89%          0.00021s        0.01531s       0.00538s           12 
    
    • 1
    • 2

    (3)调整最大停顿时间

    -XX:MaxGCPauseMillis=200    设置最大GC停顿时间指标
    
    • 1

    比如设置最大停顿时间,获取到gc日志,使用GCViewer分析吞吐量和响应时间

    Throughput       Min Pause       Max Pause      Avg Pause       GC count
      98.96%          0.00015s        0.01737s       0.00574s          12 
    
    • 1
    • 2

    (4)启动并发GC时堆内存占用百分比

    -XX:InitiatingHeapOccupancyPercent=45 
    G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比例。值为 0 则表示“一直执行GC循环)'. 默认值为 45 (例如, 全部的 45% 或者使用了45%).
    
    • 1
    • 2

    比如设置该百分比参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间

    Throughput       Min Pause       Max Pause      Avg Pause       GC count
      98.11%          0.00406s        0.00532s       0.00469s          12 
    
    • 1
    • 2

    4.2.3 G1调优最佳实战

    官网:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations)

    (1)不要手动设置新生代和老年代的大小,只要设置整个堆的大小

    why:https://blogs.oracle.com/poonam/increased-heap-usage-with-g1-gc

    G1收集器在运行过程中,会自己调整新生代和老年代的大小
    其实是通过adapt代的大小来调整对象晋升的速度和年龄,从而达到为收集器设置的暂停时间目标
    如果手动设置了大小就意味着放弃了G1的自动调优
    
    • 1
    • 2
    • 3

    (2)不断调优暂停时间目标

    一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不能总是得到满足。
    
    • 1

    (3)使用-XX:ConcGCThreads=n来增加标记线程的数量

    IHOP如果阀值设置过高,可能会遇到转移失败的风险,比如对象进行转移时空间不足。如果阀值设置过低,就会使标记周期运行过于频繁,并且有可能混合收集期回收不到空间。 
    IHOP值如果设置合理,但是在并发周期时间过长时,可以尝试增加并发线程数,调高ConcGCThreads。
    
    • 1
    • 2

    (4)MixedGC调优

    -XX:InitiatingHeapOccupancyPercent
    -XX:G1MixedGCLiveThresholdPercent
    -XX:G1MixedGCCountTarger
    -XX:G1OldCSetRegionThresholdPercent
    
    • 1
    • 2
    • 3
    • 4

    (5)适当增加堆内存大小

    (6)不正常的Full GC

    有时候会发现系统刚刚启动的时候,就会发生一次Full GC,但是老年代空间比较充足,一般是由Metaspace区域引起的。可以通过MetaspaceSize适当增加其大家,比如256M。
    
    • 1

    4.3 JVM性能优化指南

    在这里插入图片描述

  • 相关阅读:
    wsgiref模块、web框架、django框架简介
    C++程序的内存分区
    设计模式:策略模式
    【docker】dockerfile构建镜像
    算法与数据结构 学习笔记2
    [附源码]JAVA毕业设计酒店管理系统(系统+LW)
    BLDC的列子2
    【C++】Cmake简易教程与模板
    【MySQL】基于Docker搭建MySQL一主二从集群
    #AcWing--合并两个排序的链表
  • 原文地址:https://blog.csdn.net/m0_67266585/article/details/126411954