JVM调优-从垃圾回收角度来看

本文探讨了Java虚拟机(JVM)调优的关键方面,包括查看JDK GC参数,关注内存、线程锁、CPU和IO的优化。强调了确定调优目标的重要性,如低延迟或高吞吐量,并给出了不同场景下选择的垃圾回收器。新生代和老年代的内存大小调整策略被详细阐述,以减少MinorGC和FullGC的频率。此外,还分享了CMS垃圾回收器的老年代调优技巧,并提供了实际调优案例,如减少FullGC和优化CMS的重新标记阶段。

1、查看当前JDK中虚拟机运行的相关参数:

“JDK路径” -XX :+PrintFlagsFinal -version | findstr “GC”

2、调优领域

①内存:STW时间对于程序缓存影响较大
②线程锁竞争
③CPU占用
④IO

3、确定目标

针对垃圾回收,要确立自己的调优目标,是“低延迟”还是“高吞吐量”,针对目标选择合适的垃圾回收器。

  • 如果是做科学运算,调优目标是高吞吐量,选择Parallel GC。

  • 如果是做互联网项目,调优目标是低延迟,选择CMS、G1、ZGC(JDK12中)。

4、最快的 GC是不发生GC

当程序多次发生GC或Full GC,需要考虑以下问题?

  • ①数据是否太多?

  • ②数据表示是否太臃肿?
    1、查出太多无用信息
    2、对象大小 Integer 24 int 4

  • ③是否存在内存泄漏?

对于长时间存活对象,采用软弱引用的策略第三方缓存实现

5、新生代调优

(1)新生代的特点:

■所有的new操作时会先检查TLAB中是否有内存可以分配。因为内存分配也存在线程安全问题,会出现多个线程要使用同一块内存的问题,造成内存混乱。所以JVM会给每个线程分配一块私有区域:TLAB – thread-local allocation buffer

■死亡对象的回收代价是零

大部分对象用过即死,只有少数回收

■Minor GC的时间远远低于Full GC

##(2)新生代内存越大越好吗?

参数:-Xmn 设置新生代内存的初始大小和最大的大小

  • 新生代内存如果设置太小,就会频繁发生mior GC
  • 新生代内存如果设置太大,老年代的内存占用就会小,就会频繁发生Full GC

总的来说,新生代的内存还是大一些好,因为新生代主要采用的是标记整理和复制算法,标记整理算法相对于复制算法来说STW较短,而新生代大多对象都是要被回收的,所以复制算法需要复制的幸存对象并不多,占用时间也较短。所以说新生代内存还是大一些好。

估算新生代设置大小:新生代能容纳所有【并发量*(请求-响应)】的数据。

Oracle官方建议:新生代内存占用堆内存的25%到50%为最佳。

(3)幸存区大到能够保留【当前活跃对象+未来需要晋升的对象】

如果幸存区比较小,很多存活时间较短,并没有能达到晋升阈值的对象会因为JVM的动态调整,将这些对线晋升到老年代,这样就会导致这些本是短时间存活的对象一直要等到老年代内存不足引发Full GC时才会被回收。

晋升阈值要配置得当,让长时间存活对象尽快晋升。

设置晋升阈值: -XX:MaxTenuringThreshold = threshold
打印当前幸存区对象的存活状态:-XX:+PrintTenuringDistribution

6、老年代调优

以CMS为例

■CMS的老年代内存越大越好

预留更多空间给浮动垃圾。避免并发清理阶段发生并发失败

■先尝试不做调优,如果没有Full GC那么证明老年代内存充裕。即使发生Full GC也应该先尝试调优新生代

■观察发生Full GC时老年代内存占用,将老年代内存预设调大1/4~1/3

设置老年代占堆内存百分比:
-XX: CMSInitiatingOccupancyFraction=percent

7、调优案例

■案例1 Full GC和Minor GC频繁

优先调整新生代内存大小,当新生代内存大了以后,就会减少MinorGC的频繁发生;幸存区内存占比随之高了,那么晋升阈值就会变大,不会再让很多短期存活对象晋升到老年代中,占用老年代内存,导致频繁Full
GC。

■案例2请求高峰期发生Full GC,单次暂停时间特别长(CMS)

CMS中初始标记和并发标记阶段占用时间很短,一般问题会出现在重新标记阶段。重新标记阶段会使用遍历算法对新生代所有对象遍历,应该在重新标记之前对新生代对象进行一次垃圾回收,减少遍历垃圾对象耗用的时间。

-XX:+SMSScavengBeforeRemark

■案例3老年代充裕情况下,发生FullGC (1.7)

1.7及以前的版本中,永久代作为方法区的实现,永久代内存不足也会导致Full GC。增大永久代的内存空间就会解决问题。

<think> 嗯,用户现在遇到了一个具体的问题:K8s Pod的内存使用率接近99%触发了告警,但JVM堆内存显示正常,用户想知道如何通过JVM来解决这个问题。用户还提供了引用内容作为背景。 从引用内容来看,案例中的Pod内存限制是15.6G,实际使用15.5G,但JVM堆内存却显示正常。这说明问题不在堆内存,而是堆外内存或其他部分占用了大量空间。用户可能已经尝试过整堆内存参数但无效,所以才会关注堆外内存问题。 我注意到用户特别强"实际并没有什么人反馈出现问题",这说明虽然监控告警了,但应用功能暂时正常。这种"隐性危险"往往更让人焦虑,用户需要尽快找出潜在的内存黑洞。 这个问题需要分几个层面来解决: 首先是诊断,需要用NMT等工具确认具体的内存占用来源;然后是,针对性地整元空间、线程栈、堆外缓存等参数;最后还需要考虑容器环境的特殊性,比如cgroup内存限制对JVM的影响。 用户可能不太清楚的是:在容器环境中,JVM默认不会感知cgroup内存限制,这会导致堆大小设置不当。需要特别强-XX:+UseContainerSupport参数的重要性。 另外,引用案例中提到"云排查"方法,说明可能需要结合容器的监控工具来辅助分析,这部分可以补充一些具体命令,比如查看cgroup内存统计的路径。 考虑到用户是技术人员,回复应该包含具体的参数配置示例和排查命令,但也要解释清楚每个步骤的原理。比如解释为什么元空间泄漏会导致内存增长,以及如何用jcmd命令来dump内存详情。 最后要提醒用户:后必须进行压力测试验证,因为某些参数整(比如减少线程栈大小)在高并发时可能导致栈溢出。 </think>针对K8s Pod内存使用率接近99%告警的JVM方法,需结合堆内外内存分析,具体步骤如下: ### 一、核心问题定位 1. **确认内存分配来源** ```bash kubectl exec <pod_name> -- jcmd 1 VM.native_memory summary ``` 输出中关注: - `Total (reserved)`:JVM总预留内存 - `Java Heap`:堆内存 - `Class`:类元数据 - `Thread`:线程栈 - `GC`:垃圾回收器占用 - `Internal`:直接内存/堆外内存(如NIO Buffer) 2. **检查容器内存限制感知** ```bash kubectl exec <pod_name> -- java -XX:+PrintFlagsFinal -version | grep UseContainerSupport ``` 确保输出为 `UseContainerSupport = true`(JDK8u191+默认开启),否则JVM无法识别cgroup内存限制。 --- ### 二、关键策略 #### 1. **堆内存化** - **动态整堆比例**(避免`-Xmx`等于Pod内存上限) ```bash -XX:MaxRAMPercentage=75.0 # 限制堆最大占用Pod内存的75% -XX:InitialRAMPercentage=50.0 ``` - **启用弹性元空间**(防Metaspace泄漏) ```bash -XX:MaxMetaspaceSize=256m # 限制元空间上限 -XX:MetaspaceSize=128m ``` #### 2. **堆外内存控制** - **限制直接内存** ```bash -XX:MaxDirectMemorySize=512m # 限制NIO DirectBuffer ``` - **压缩指针化**(64G以下内存开启) ```bash -XX:+UseCompressedOops # 压缩普通对象指针 -XX:+UseCompressedClassPointers # 压缩类指针 ``` #### 3. **线程资源化** ```bash -XX:CICompilerCount=2 # 减少JIT编译线程 -Xss512k # 降低单个线程栈大小(默认1M) ``` #### 4. **容器化专属参数** ```bash -XX:+UseContainerSupport # 确保开启容器支持 -XX:ActiveProcessorCount=2 # 限制CPU核数避免过度分配 ``` --- ### 三、监控验证手段 1. **实时内存分析** ```bash kubectl exec <pod_name> -- jmap -histo:live <pid> # 对象分布统计 kubectl exec <pod_name> -- jcmd <pid> GC.heap_dump /tmp/dump.hprof # 堆转储 ``` 2. **监控堆外内存** ```bash kubectl exec <pod_name> -- jcmd <pid> VM.native_memory baseline # 建立基线 kubectl exec <pod_name> -- jcmd <pid> VM.native_memory detail.diff # 对比变化 ``` 3. **容器级监控** ```bash kubectl top pod <pod_name> --containers # 实时内存/CPU ``` --- ### 四、典型配置示例 ```bash java \ -XX:+UseContainerSupport \ -XX:MaxRAMPercentage=75.0 \ -XX:InitialRAMPercentage=50.0 \ -XX:MaxMetaspaceSize=256m \ -XX:MaxDirectMemorySize=512m \ -XX:+UseCompressedOops \ -Xss512k \ -XX:ActiveProcessorCount=2 \ -jar your-app.jar ``` > **后效果**:某案例中Pod内存限制15.6G,化后实际使用从15.5G降至8.2G,主要节省来自[堆外内存控制](#_2-堆外内存控制)和[线程资源化](#_3-线程资源化)[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值