jvm之内存调优

本文详细探讨了JVM内存调优的重要性,指出系统级调优目标是减少GC频率和Full GC次数,重点关注Full GC触发因素。介绍了新生代、旧生代、Survivor区的合理设置,以及如何通过JVM提供的工具进行监控。提到了-XX:MaxTenuringThreshold、吞吐量优先和暂停时间优先的GC策略,并列举了常见的JVM内存参数,包括堆、收集器和元空间的设置,以及针对不同场景的优化建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。

        对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理,导致Full GC一般由于以下几种情况:

  • 旧生代空间不足:调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象 
  • Pemanet Generation空间不足:增大Perm Gen空间,避免太多静态对象 
  • 统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间,控制好新生代和旧生代的比例 
  • System.gc()被显示调用:垃圾回收不要手动触发,尽量依靠JVM自身的机制 

        调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果

1)新生代设置过小

一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发Full GC

2)新生代设置过大

一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发Full GC;二是新生代GC耗时大幅度增加

一般说来新生代占整个堆1/3比较合适

3)Survivor设置过小

导致对象从eden直接到达旧生代,降低了在新生代的存活时间

4)Survivor设置过大

导致eden过小,增加了GC频率

另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收

由上一篇博文JVM学习笔记(三)------内存管理和垃圾回收可知新生代和旧生代都有多种GC策略和组合搭配,选择这些策略对于我们这些开发人员是个难题,JVM提供两种较为简单的GC策略的设置方式

1)吞吐量优先

JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置

2)暂停时间优先

JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置

最后汇总一下JVM常见配置

  1. 堆设置
    • -Xms:初始堆大小
    • -Xmx:最大堆大小
    • -XX:NewSize=n:设置年轻代大小
    • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
    • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
    • -XX:MaxPermSize=n:设置持久代大小
  2. 收集器设置
    • -XX:+UseSerialGC:设置串行收集器虚拟机运行在Client 模式下的默认值,打开此开关后,使用Serial + Serial Old 的收集器组合进行内存回收
    • -XX:+UseParallelGC:设置并行收集器,虚拟机运行在Server 模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收。
    • -XX:+UseParalledlOldGC:设置并行年老代收集器。打开此开关后,使用Parallel Scavenge + Parallel Old 的收集器组合进行内存回收。
    • -XX:+UseConcMarkSweepGC:设置并发收集器,打开此开关后,使用ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为CMS 收集器出现Concurrent Mode Failure失败后的后备收集器使用。(我们的线上服务用的都是这个),年轻代将会使用ParNew收集器
    • -XX:+UseG1GC,打开此开关后,采用 Garbage First (G1) 收集器
    • -XX:+UseParNewGC,在JDK1.8被废弃,在JDK1.7还可以使用。打开此开关后,使用ParNew + Serial Old 的收集器组合进行内存回收。

    参考文献:

    GC参数解析 UseSerialGC、UseParNewGC、UseParallelGC、UseConcMarkSweepGC_Leo187的博客-优快云博客

  3. 垃圾回收统计信息
    • -XX:+PrintGC
    • -XX:+PrintGCDetails
    • -XX:+PrintGCTimeStamps
    • -Xloggc:filename
  4. 并行收集器设置
    • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
    • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
    • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
  5. 并发收集器设置
    • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
    • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

 常见问题整理:

-Xss 栈大小,默认1M。一般256k足够。

问题:

1、新创建的线程从哪里申请内存?堆吗?

        线程从物理机申请内存,是非堆

2、怎么可以创建更多的线程?

        减小Xss、减小Xmx或者增加机器内存(还有其他,例如减小堆外、code cache metaspace等)

-Xms:初始堆内存大小,默认物理内存64/1

-Xmx:最大堆内存,默认物理内存4/1

-Xmn:新生代大小

-XX:NewRatio=2 老年代的占比,2代表老年代:新生代=2:1

-XX:SurvivorRatio=8:新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8,代表eden:survivor1:Survivor2 = 8:1:1

-XX:MaxTenuringThreshold:晋升老年代的年龄阈值,默认15

-XX:PretenureSizeThreshold:大对象进入老年代的阈值

-XX:CMSInitiatingOccupancyFraction:老年代占用的阈值,触发并发收集

问题:

1、为什么一般将Xmx和Xms设置成一样的值?

        heap在扩容和缩容时,会触发gc。例如:在项目启动时可能会频繁gc

2、什么情况下对象会晋升到老年代:

        1)大对象:所谓的大对象是指需要大量连续内存空间的java对象,最典型的大对象就是那种很长的字符串以及数组,大对象对虚拟机的内存分配就是坏消息,尤其是一些朝生夕灭的短命大对象,写程序时应避免。

        2)长期存活的对象:虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,。对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当他的年龄增加到一定程度(默认是15岁), 就将会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

        3)动态对象年龄判定:为了能更好地适应不同程度的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

        4)在一次安全Minor GC 中,仍然存活的对象不能在另一个Survivor 完全容纳,则会通过担保机制进入老年代

3、为什么要将大对象直接放入老年代?

        避免大对象在新生代,屡次躲过GC,还得把他们来复制来复制去的,最后才进入老年代。minor gc会STW,复制算法,大对象耗时

4、什么场景需要调整SurvivorRatio?

        SurvivorRatio,它定义了新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10。有些对象在短时间内会继续存活,但是也不会太久,一般是增大survivor区域,避免对象由于(1~n年龄的对象达到50%),被晋升老年代,引起频繁的fullgc

5、如果98%的时间发生在STW上,且回收对象不到堆的2%,会怎么样?

        OOM,可以-XX:-UseGCOverheadLimit禁用此功能

6、Concurrent Mode Failure 的原因是什么? 后果是什么?怎么优化?

        原因 老年代正在清理的过程中,又有新对象进入老年代,此时老年代没有空间容纳,

实操中,-XX:CMSInitiatingOccupancyFraction 比例参数设置过大,或者没有开启full gc压缩 或者年轻代对象晋升老年代过快。触发serial collector,串行收集。STW。调整垃圾回收时机(例如:CMSInitiatingOccupancyFraction),增加老年代(没有内存泄漏的话)

7、修改CMSInitiatingOccupancyFraction的原因是什么?

        调整过小会导致频繁fullgc,太大会导致serial collector

8,yonggc stw是否是stw,对系统影响为什么比 fullgc 影响小

        是,但是新生代的 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般在这里使用速度快、效率高的算法,使垃圾回收能尽快完成。所以stw的时间相对少一点。

9,优化young gc和older gc的次数

        代码层面:减少创建对象数量,减少日志输出,使用StringBuilder或StringBuffer来代替String

        将进入老年代的对象数量降到最低:老年代GC相对来说会比新生代GC更耗时,因此,减少进入老年代的对象数量可以显著降低Full GC的频率。减少Full GC的执行时间:Full GC的执行时间比Minor GC要长很多,因此,如果在Full GC上花费过多的时间(超过1s),将可能出现超时错误

10,GC优化的过程

        1.监控GC状态

        2.分析监控结果后决定是否需要优化GC如果分析结果显示运行GC的时间只有0.1-0.3秒,那么就不需要把时间浪费在GC优化上,但如果运行GC的时间达到1-3秒,甚至大于10秒,那么GC优化将是很有必要的。

        3.设置GC类型/内存大小

元空间(存放类和方法的元数据、字节码)

-XX:MetaspaceSize

-XX:MaxMetaspaceSize

元数据空间大小

-XX:CompressedClassSpaceSize

压缩对象指针空间

问题:

1、-XX:MetaspaceSize、-XX:MaxMetaspaceSize为什么一般要设置同样大小?

        扩容会触发fullgc

2、怎么设置一个相对合理的值?

        通过项目运行稳定后,查看真实占用(此时:-XX:MetaspaceSize、-XX:MaxMetaspaceSize设置是不一样的)

3、CompressedClassSpaceSize的作用?

        设置压缩对象指针的空间的大小,这块默认1G,一般占用较小,可以优化

4、怎么触发metaspace的OOM?

        Cglib动态代理,动态产生新的类

code cache(jit 编译的机器码)

-XX:ReservedCodeCacheSize

-XX:InitialCodeCacheSize

        如果code cache满的话,会导致jit关闭,系统性能急剧下降

优秀参考博文:

JVM常用调优参数 ——JVM篇_jvm调优_YING—country的博客-优快云博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值