jvm调优

GC的基础知识

1.什么是垃圾

    C语言申请内存:malloc free
    C++: new delete
    c/C++ 手动回收内存
    Java: new ?
    自动内存回收,编程上简单,系统不容易出错,手动释放内存,容易出两种类型的问题:
    忘记回收
    多次回收
    没有任何引用指向的一个对象或者多个对象(循环引用)

2.如何定位垃圾

    引用计数(ReferenceCount)
    根可达算法(RootSearching)

3.常见的垃圾回收算法

    标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描)
    拷贝算法 (copying) - 没有碎片,浪费空间
    标记压缩(mark compact) - 没有碎片,效率偏低(两遍扫描,指针需要调整)

4.JVM内存分代模型(用于分代垃圾回收算法)

    1.部分垃圾回收器使用的模型

        除Epsilon ZGC Shenandoah之外的GC都是使用逻辑分代模型
        G1是逻辑分代,物理不分代
        除此之外不仅逻辑分代,而且物理分代

    2.新生代 + 老年代 + 永久代(1.7)Perm Generation/ 元数据区(1.8) Metaspace

        1.永久代 元数据 - Class
        2.永久代必须指定大小限制 ,元数据可以设置,也可以不设置,无上限(受限于物理内存)
        3.字符串常量 1.7 - 永久代,1.8 - 堆
        4.MethodArea逻辑概念 - 永久代、元数据

    3.新生代 = Eden + 2个suvivor区

        1.YGC回收之后,大多数的对象会被回收,活着的进入s0
        2.再次YGC,活着的对象eden + s0 -> s1
        3.再次YGC,eden + s1 -> s0
        4.年龄足够 -> 老年代 (15 CMS 6)
        5.s区装不下 -> 老年代

    4.老年代
        1.顽固分子
        2.老年代满了FGC Full GC

        5.GC Tuning (Generation)

        1.尽量减少FGC
        2.MinorGC = YGC
        3.MajorGC = FGC
    
        6.动态年龄:https://www.jianshu.com/p/989d3b06a49d

        7.分配担保:YGC期间 survivor区空间不够了 空间担保直接进入老年代参考:https://cloud.tencent.com/developer/article/1082730
    
5.常见的垃圾回收器
    1.JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前任何一个JDK版本默认是CMS
        并发垃圾回收是因为无法忍受STW
    2.Serial 年轻代 串行回收
    3.PS 年轻代 并行回收
    4.ParNew 年轻代 配合CMS的并行回收
    5.SerialOld
    6.ParallelOld
    7.ConcurrentMarkSweep 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms) CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定
        CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收
        想象一下:PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW)几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC
        算法:三色标记 + Incremental Update
    8.G1(10ms)算法:三色标记 + SATB
    9.ZGC (1ms) PK C++ 算法:ColoredPointers + LoadBarrier
    10 Shenandoah 算法:ColoredPointers + WriteBarrier
    11.Eplison
    12.PS 和 PN区别的延伸阅读:▪https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73

    13.垃圾收集器跟内存大小的关系

        1.Serial 几十兆
        2.PS 上百兆 - 几个G
        3.CMS - 20G
        4.G1 - 上百G
        5.ZGC - 4T - 16T(JDK13)
        
JVM调优第一步,了解JVM常用命令行参数

    JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
    区分概念:内存泄漏memory leak,内存溢出out of memory
    java -XX:+PrintCommandLineFlags HelloGC
    java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC  HelloGC
    PrintGCDetails PrintGCTimeStamps PrintGCCauses
    java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
    java -XX:+PrintFlagsInitial 默认参数值
    java -XX:+PrintFlagsFinal 最终参数值
    java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
    java -XX:+PrintFlagsFinal -version |grep GC
    
调优前的基础概念:

    吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
    响应时间:STW越短,响应时间越好

    所谓调优,首先确定,追求啥?吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量...
    问题:
    科学计算,吞吐量。数据挖掘,thrput。吞吐量优先的一般:(PS + PO)
    响应时间:网站 GUI API (1.8 G1)

什么是调优?

    1.根据需求进行JVM规划和预调优
    2.优化运行JVM运行环境(慢,卡顿)
    3.解决JVM运行过程中出现的各种问题(OOM)

调优,从规划开始

    1.调优,从业务场景开始,没有业务场景的调优都是耍流氓
    2.无监控(压力测试,能看到结果),不调优

    3.步骤:
        1.熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)
            1.响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
            2吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]
        2.选择回收器组合
        3.计算内存需求(经验值 1.5G 16G)
        4.选定CPU(越高越好)
        5.设定年代大小、升级年龄
        6.设定日志参数
            1.-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
            2.或者每天产生一个日志文件

        7.观察日志情况

优化环境

    1.有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了

        1.为什么原网站慢?很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢
        2.为什么会更卡顿?内存越大,FGC时间越长
        3.咋办?PS -> PN + CMS 或者 G1

    2.系统CPU经常100%,如何调优?CPU100%那么一定有线程在占用系统资源,

        1.找出哪个进程cpu高(top)
        2.该进程中的哪个线程cpu高(top -Hp)
        3.导出该线程的堆栈 (jstack)
        4.查找哪个方法(栈帧)消耗时间 (jstack)
        5.工作线程占比高 | 垃圾回收线程占比高

    3.系统内存飙高,如何查找问题?

        1.导出堆内存 (jmap)
        2.分析 (jhat jvisualvm mat jprofiler ... )

    4.如何监控JVM

        1.jstat jvisualvm jprofiler arthas top...

解决JVM运行中的问题

    1.java -Xms200M -Xmx200M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01

    2.一般是运维团队首先受到报警信息(CPU Memory)

    3.top命令观察到问题:内存不断增长 CPU占用率居高不下

    4.top -Hp 观察进程中的线程,哪个线程CPU和内存占比高

    5.jps定位具体java进程jstack 定位线程状况,重点关注:WAITING BLOCKED
        eg.waiting on <0x0000000088ca3310> (a java.lang.Object)
        假如有一个进程中100个线程,很多线程都在waiting on  ,一定要找到是哪个线程持有这把锁
        怎么找?搜索jstack dump的信息,找 ,看哪个线程持有这把锁RUNNABLE

    6.为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称 怎么样自定义线程池里的线程名称?(自定义ThreadFactory)

    7.jinfo pid

    8.jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)
        jstat -gc 4655 500 : 每个500个毫秒打印GC的情况

    9.jmap - histo 4655 | head -20,查找有多少对象产生

    10.jmap -dump:format=b,file=xxx pid :
        线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿
        1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件
        2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响

    11.java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError com.mashibing.jvm.gc.T15_FullGC_Problem01

    12.使用MAT / jhat /jvisualvm 进行dump文件分析 https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html
        jhat -J-mx512M xxx.dump http://192.168.17.11:7000 拉到最后:找到对应链接 可以使用OQL查找特定问题对象

    13.找到代码的问题


GC常用参数

    -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间
    -XX:+UseTLAB 使用TLAB,默认打开
    -XX:+PrintTLAB 打印TLAB的使用情况
    -XX:TLABSize 设置TLAB大小
    -XX:+DisableExplictGC System.gc()不管用 ,FGC
    -XX:+PrintGC
    -XX:+PrintGCDetails
    -XX:+PrintHeapAtGC
    -XX:+PrintGCTimeStamps
    -XX:+PrintGCApplicationConcurrentTime (低)打印应用程序时间
    -XX:+PrintGCApplicationStoppedTime (低)打印暂停时长
    -XX:+PrintReferenceGC (重要性低)记录回收了多少种不同引用类型的引用
    -verbose:class 类加载详细过程
    -XX:+PrintVMOptions
    -XX:+PrintFlagsFinal  -XX:+PrintFlagsInitial必须会用
    -Xloggc:opt/log/gc.log
    -XX:MaxTenuringThreshold 升代年龄,最大值15
    锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 ...这些不建议设置

Parallel常用参数

-XX:SurvivorRatio
-XX:PreTenureSizeThreshold 大对象到底多大
-XX:MaxTenuringThreshold
-XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
-XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

CMS常用参数

-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads CMS线程数量
-XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
-XX:+UseCMSCompactAtFullCollection 在FGC时进行压缩
-XX:CMSFullGCsBeforeCompaction 多少次FGC之后进行压缩
-XX:+CMSClassUnloadingEnabled
-XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm回收
GCTimeRatio 设置GC时间占用程序运行时间的百分比
-XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

G1常用参数

-XX:+UseG1GC
-XX:MaxGCPauseMillis 建议值,G1会尝试调整Young区的块数来达到这个值
-XX:GCPauseIntervalMillis ?GC的间隔时间
-XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长ZGC做了改进(动态区块大小)
G1NewSizePercent新生代最小比例,默认为5%
G1MaxNewSizePercent新生代最大比例,默认为60%
GCTimeRatioGC时间建议比例,G1会根据这个值调整堆空间
ConcGCThreads线程数量
InitiatingHeapOccupancyPercent启动G1的堆空间占用比例

参考资料

1.https://blogs.oracle.com/jonthecollector/our-collectors
2.https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
3.http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
4.JVM调优参考文档:https://docs.oracle.com/en/java/javase/13/gctuning/introduction-garbage-collection-tuning.html#GUID-8A443184-7E07-4B71-9777-4F12947C8184
5.https://www.cnblogs.com/nxlhero/p/11660854.html 在线排查工具
6.https://www.jianshu.com/p/507f7e0cc3a3 arthas常用命令
7.Arthas手册:

    1.启动arthas java -jar arthas-boot.jar
    2.绑定java进程
    3.dashboard命令观察系统整体情况
    4.help 查看帮助
    5.help xx 查看具体命令帮助

8.jmap命令参考: https://www.jianshu.com/p/507f7e0cc3a3

    1.jmap -heap pid
    2.jmap -histo pid
    3.jmap -clstats pid

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值