JVM性能参数调优实践,不会执行Full GC,网站无停滞

本文分享了在高PV环境下网站JVM参数调优的经验,包括64位系统的优势、合理配置内存参数、使用并发回收策略等内容,并给出了具体的配置实例。

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

JVM参数调优是个很头痛的问题,设置的不好,JVM不断执行Full GC,导致整个系统变得很慢,网站停滞时间能达10秒以上,这种情况如果没隔几分钟就来一次,自己都受不了。这种停滞在测试的时候看不出来,只有网站pv达到数十万/天的时候问题就暴露出来了。 

要想配置好JVM参数,需要对年轻代、年老代、救助空间和永久代有一定了解,还要了解jvm内存管理逻辑,最终还要根据自己的应用来做调整。关于JVM参数上网一搜就能搜出一大把,也有很多提供实践的例子,我也按照各种例子测试过,最终还是会出现问题。 

经过几个月的实践改善,我就网站(要求无停滞时间)的jvm参数调优给出以下几条经验。 

1:建议用64位操作系统,Linux下64位的jdk比32位jdk要慢一些,但是吃得内存更多,吞吐量更大。 

2:XMX和XMS设置一样大,MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力。 

3:调试的时候设置一些打印参数,如-XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log,这样可以从gc.log里看出一些端倪出来。 

4:系统停顿的时候可能是GC的问题也可能是程序的问题,多用jmap和jstack查看,或者killall -3 java,然后查看java控制台日志,能看出很多问题。有一次,网站突然很慢,jstack一看,原来是自己写的URLConnection连接太多没有释放,改一下程序就OK了。 

5:仔细了解自己的应用,如果用了缓存,那么年老代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定。 

6:垃圾回收时promotion failed是个很头痛的问题,一般可能是两种原因产生,第一个原因是救助空间不够,救助空间里的对象还不应该被移动到年老代,但年轻代又有很多对象需要放入救助空间;第二个原因是年老代没有足够的空间接纳来自年轻代的对象;这两种情况都会转向Full GC,网站停顿时间较长。第一个原因我的最终解决办法是去掉救助空间,设置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二个原因我的解决办法是设置CMSInitiatingOccupancyFraction为某个值(假设70),这样年老代空间到70%时就开始执行CMS,年老代有足够的空间接纳来自年轻代的对象。 

7:不管怎样,永久代还是会逐渐变满,所以隔三差五重起java服务器是必要的,我每天都自动重起。 

8:采用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿。 

我的最终配置如下(系统8G内存),每天几百万pv一点问题都没有,网站没有停顿,2009年shedewang.com没有因为内存问题down过机。 

$JAVA_ARGS .= " -Dresin.home=$SERVER_ROOT -server -Xms6000M -Xmx6000M -Xmn500M -XX:PermSize=500M -XX:MaxPermSize=500M -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0 -Xnoclassgc -XX:+DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=90 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log "; 

说明一下, -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0就是去掉了救助空间; 
-Xnoclassgc禁用类垃圾回收,性能会高一点; 
-XX:+DisableExplicitGC禁止System.gc(),免得程序员误调用gc方法影响性能; 
-XX:+UseParNewGC,对年轻代采用多线程并行回收,这样收得快; 
带CMS参数的都是和并发回收相关的,不明白的可以上网搜索; 
CMSInitiatingOccupancyFraction,这个参数设置有很大技巧,基本上满足(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn就不会出现promotion failed。在我的应用中Xmx是6000,Xmn是500,那么Xmx-Xmn是5500兆,也就是年老代有5500兆,CMSInitiatingOccupancyFraction=90说明年老代到90%满的时候开始执行对年老代的并发垃圾回收(CMS),这时还剩10%的空间是5500*10%=550兆,所以即使Xmn(也就是年轻代共500兆)里所有对象都搬到年老代里,550兆的空间也足够了,所以只要满足上面的公式,就不会出现垃圾回收时的promotion failed; 
SoftRefLRUPolicyMSPerMB这个参数我认为可能有点用,官方解释是softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap,我觉得没必要等1秒; 

网上其他介绍JVM参数的也比较多,估计其中大部分是没有遇到promotion failed,或者访问量太小没有机会遇到,(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn这个公式绝对是原创,真遇到promotion failed了,还得这么处理。
### Java 释放锁非原子性分析 在 Java 中,`ReentrantLock` 是基于 `AbstractQueuedSynchronizer (AQS)` 实现的同步工具类。当线程尝试获取锁时,如果当前锁未被其他线程占用,则会通过 CAS 操作更新 AQS 的 `state` 变量来标记锁已被占有[^3]。然而,释放锁的过程并非完全原子化。 #### 非原子性的原因 1. **解锁过程中的多个步骤** 解锁操作通常涉及两个主要部分:减少 `state` 计数器并唤醒等待队列中的下一个线程。具体来说: - 如果当前线程是唯一持有锁的线程(即 `state == 1`),那么它只需将 `state` 设置为 0 并通知等待队列中的第一个线程。 - 如果存在重入情况(即 `state > 1`),则需要逐步递减 `state` 直至其等于 0 才能真正释放锁。 上述逻辑表明,即使减少了 `state` 值,也可能由于并发环境下的上下文切换或其他干扰因素而未能及时完成后续的通知动作,从而导致短暂的时间窗口,在此期间可能有其他线程误认为锁已经被释放。 2. **Happens-Before 关系的影响** 根据 JMM 定义,虽然每次修改共享变量前都会强制将其最新值刷回主存以确保可见性[^4],但在某些极端情况下仍可能出现异常行为。例如,假设某一线程刚刚完成了对某个资源的操作准备交还控制权给另一方;但由于度延迟等原因使得实际提交结果滞后于预期时间点——此时若有第三方介入抢占了原本属于目标接收者的权限就会引发竞争条件问题。 #### Full GC 导致阻塞的原因 垃圾收集机制本身是一个复杂且耗时的任务,尤其是在发生 Full GC 事件时更是如此。这是因为整个堆空间都需要被扫描清理一遍才能继续正常运作下去: - 当 JVM 发起一次完整的垃圾回收周期时,所有应用程序级别的活动都将暂停下来以便让系统能够集中精力处理内存管理事务; - 对于那些依赖特定对象实例或者引用关系维持状态稳定的应用场景而言,这种长时间停滞无疑会造成显著影响甚至崩溃风险增加。 因此可以理解为每当遇到大规模分配请求失败触发紧急措施启动全局整理流程之后,任何正在进行当中涉及到这些受影响区域内的计算单元都有极大可能性经历同程度上的延缓现象直至相应阶段结束为止。 --- ### 解决方法探讨 针对上述提到的现象及其潜在危害,可以从以下几个方面入手寻找解决方案: 1. **化代码设计降低争用概率** - 尽量缩短临界区范围,只保护确实必要的最小限度的数据结构或功能模块; - 使用读写分离策略如 ReadWriteLock 来平衡同类型的访问需求之间的矛盾冲突; 2. **引入更高效的数据存储形式规避传统 HashMap 局限性** - 替代方案之一便是 ConcurrentHashMap ,它内部采用了分段锁定技术有效缓解单点瓶颈效应的同时保持较高的吞吐率水平 ; 3. **JVM 参数配置提升性能表现** - 合理设置新生代大小比例参数(-Xmn),增大 Eden 区容量有助于减少 MinorGC频率进而间接减轻压力传导至老年代最终诱发FullGc的可能性; - 开启 G1 或 ZGC 新型算法替代默认CMS模式可以获得更好的实时性和扩展能力支持 ; 4. **监控预警提前干预处置突发状况** - 利用专门的日志记录工具跟踪观察内存消耗趋势变化规律找出隐藏隐患所在位置加以改进完善预防未来再次遭遇相似困境. ```java // 示例: 如何正确使用 ReentrantLock 和 try-finally 结构保障安全性 public class SafeCounter { private int counter = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { counter++; } finally { lock.unlock(); // 确保无论是否抛出异常都能释放锁 } } public int getCounterValue(){ return counter; } } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值