JVM学习笔记(6) 第二部分 自动内存管理 第5章节 调优案例分析与实战

文章目录

  • 第5章:调优案例分析与实战
    • [5.0 个人感悟](#5.0 个人感悟)
    • [5.1 概述](#5.1 概述)
    • [5.2 案例分析](#5.2 案例分析)
      • [5.2.1 高性能硬件上的程序部署策略](#5.2.1 高性能硬件上的程序部署策略)
      • [5.2.2 集群间同步导致的内存溢出](#5.2.2 集群间同步导致的内存溢出)
      • [5.2.3 堆外内存导致的溢出错误](#5.2.3 堆外内存导致的溢出错误)
      • [5.2.4 外部命令导致系统缓慢](#5.2.4 外部命令导致系统缓慢)
      • [5.2.5 服务器JVM进程崩溃](#5.2.5 服务器JVM进程崩溃)
      • [5.2.6 不恰当数据结构导致内存占用过大](#5.2.6 不恰当数据结构导致内存占用过大)
      • [5.2.7 由Windows虚拟内存导致的长时间停顿](#5.2.7 由Windows虚拟内存导致的长时间停顿)
      • [5.2.8 安全点导致长时间停顿](#5.2.8 安全点导致长时间停顿)
    • [5.3 实战:Eclipse运行速度调优](#5.3 实战:Eclipse运行速度调优)
    • [5.4 常用调优参数](#5.4 常用调优参数)
    • [5.5 本章小结](#5.5 本章小结)

第5章:调优案例分析与实战

5.0 个人感悟

  • 理论要结合实践
  • 书中很多案例可能比较早了,但是还是能提供很多思路,jdk也越来越新,掌握知识,工作中多实践
  • JVM问题的解决不是玄学,都是是基于数据和逻辑的诊断

5.1 概述

目的

将前面章节的理论(内存模型、垃圾回收、监控工具)应用到实际问题的解决中

内容

  • 8个来自真实生产环境的典型案例
  • 实战:Eclipse运行速度调优,有兴趣可以看看原文

5.2 案例分析

5.2.1 高性能硬件上的程序部署策略

场景

一个15万PV/天的文档网站,硬件升级为4个CPU、16GB内存,64位JDK 1.5,堆内存设为12GB。升级后网站经常不定期出现长时间失去响应

原因:GC停顿导致。使用吞吐量优先收集器,一次Full GC停顿高达14秒。访问文档时产生大量大对象直接进入老年代,内存迅速被消耗

解决方案:采用逻辑集群方案,建立5个32位JDK的逻辑集群,每个堆固定1.5GB,前端用Apache做负载均衡,并改为CMS收集器

要点:大内存部署需控制Full GC频率;64位JDK存在指针膨胀、性能略低、dump难以分析等问题

5.2.2 集群间同步导致的内存溢出

场景:一个基于B/S的MIS系统,6个节点的亲合式集群。使用JBossCache构建全局缓存实现节点间数据共享,最近频繁出现内存溢出

原因:服务端有一个安全校验全局Filter,每次请求都更新最后操作时间并同步到所有节点。当网络传输不满足要求时,JGroups协议栈中的NAKACK重发数据在内存中不断堆积

解决方案:改进实现方式,避免频繁的集群间同步操作,或升级JBossCache版本

要点:集群缓存同步需考虑网络状况;频繁写操作会带来很大的网络同步开销

5.2.3 堆外内存导致的溢出错误

场景:一个学校考试系统,运行在32位Windows系统(4GB内存)。使用CometD 1.1.1框架做服务器推送,堆内存调到1.6GB后仍不定时抛出OOM,且不产生heapdump文件

原因:CometD框架大量使用NIO操作,需要直接内存(堆外内存)。32位Windows进程最大内存2GB,堆占用1.6GB后,剩余空间不足以支撑直接内存分配,且直接内存只能等Full GC时"顺便"回收

解决方案 :通过-XX:MaxDirectMemorySize限制堆外内存上限,或升级到64位JDK。

要点:除堆和方法区外,直接内存、线程栈、Socket缓存区、JNI代码等也会占用较多内存,总和受操作系统进程最大内存限制

5.2.4 外部命令导致系统缓慢

场景:一个数字校园应用系统,运行在4 CPU的Solaris 10上,GlassFish中间件。大并发压力测试时请求响应慢,mpstat显示CPU使用率很高,但占用CPU资源的并非应用本身

原因 :最消耗CPU资源的是"fork"系统调用。每个用户请求的处理都会通过Runtime.getRuntime().exec()执行外部shell脚本获取系统信息。频繁调用时,创建进程的开销非常可观

解决方案:去掉Shell脚本执行语句,改用Java API获取信息后,系统很快恢复正常

要点Runtime.getRuntime().exec()会先克隆当前进程再执行外部命令,频繁调用会消耗大量CPU和内存

5.2.5 服务器JVM进程崩溃

场景 :与案例5.2.2相同的MIS系统。正常运行一段时间后,频繁出现JVM进程自动关闭,留下hs_err_pid###.log文件

原因:系统最近与OA门户做了集成,待办事项变化时通过Web服务同步。OA系统接口响应慢(长达3分钟),虽然使用了异步调用,但累积的未完成Web服务越来越多,导致等待的线程和Socket连接超出虚拟机承受能力,最终进程崩溃

解决方案:通知OA门户方修复接口,并将异步调用改为生产者/消费者模式的消息队列实现

要点:异步调用不能解决速度不对等问题;消息队列可削峰填谷,避免资源累积

5.2.6 不恰当数据结构导致内存占用过大

场景 :一个后台RPC服务器,64位JDK,堆内存4~8GB(新生代1GB),使用ParNew+CMS。平时Minor GC约30毫秒,但每10分钟加载一个80MB数据文件到内存分析,形成超过100万个HashMap<Long,Long> Entry,此时Minor GC停顿超过500毫秒

原因 :分析数据文件期间,Eden空间被大量存活对象填满。ParNew使用复制算法,存活对象过多时复制开销沉重。根本原因是HashMap<Long,Long>的空间效率太低------有效数据仅16字节,实际占用约88字节,效率约18%

解决方案 (GC调优角度):去掉Survivor空间(-XX:SurvivorRatio=65536-XX:MaxTenuringThreshold=0),让存活对象在第一次Minor GC后直接进入老年代

治本方案 :修改程序,使用更高效的数据结构(如Trove、Eclipse Collections,或直接用long原始类型的数组)

要点HashMap<Long,Long>存储基本类型数据时,对象头和指针的开销远大于有效数据,空间效率极低。

5.2.7 由Windows虚拟内存导致的长时间停顿

场景:一个带心跳检测的GUI桌面程序,每15秒发送一次心跳信号。程序上线后心跳检测有误报,日志显示程序偶尔会间隔约一分钟无日志输出,处于停顿状态

原因:GC停顿导致。GC日志显示真正执行GC的时间不长,但从准备开始GC到真正开始GC之间消耗了绝大部分时间。程序最小化时,工作内存被操作系统交换到磁盘页面文件中,GC时需要恢复页面文件导致异常停顿

解决方案 :加入-Dsun.awt.keepWorkingSetOnMinimize=true参数,保证程序恢复最小化时工作内存不被交换到磁盘。VisualVM等AWT程序也使用此参数

要点:桌面GUI程序最小化时内存可能被交换到磁盘,导致恢复时GC停顿异常增加。

5.2.8 安全点导致长时间停顿

场景 :一个离线HBase集群,JDK 8,使用G1收集器,设置了-XX:MaxGCPauseMillis=500(最大暂停时间500毫秒)。运行后发现GC停顿经常超过3秒,且实际GC动作只占其中几百毫秒

原因 :安全点等待耗时过长。添加-XX:+PrintSafepointStatistics参数后,日志显示有两个线程特别慢,导致GC线程长时间自旋等待。排查发现,HBase的RpcServer线程中有一个连接超时清理函数,循环索引是int类型------HotSpot默认不会在可数循环(Counted Loop)中放置安全点,当垃圾收集发生时,必须等待该循环全部跑完才能进入安全点

解决方案 :将循环索引的数据类型从int改为long(使循环变成不可数循环,强制插入安全点),问题得以解决

要点 :HotSpot默认使用int或更小范围的循环索引不会放置安全点;可数循环若单次执行很慢,仍会导致长时间等待;JDK 8下-XX:+UseCountedLoopSafepoints参数有Bug。

5.3 实战:Eclipse运行速度调优

场景:Eclipse启动缓慢(约15秒),GC时间、类加载时间、JIT编译时间占用了大量用户程序时间,且使用过程中经常有不时的停顿感[reference:31]。

调优手段

  • 升级JDK版本(JDK 6比JDK 5有约15%的性能提升)[reference:32]。
  • 调整堆内存分配参数(新生代/老年代比例)。
  • 调整编译线程数等参数。

要点:版本升级可带来"免费的"性能提升;通过VisualVM和VisualGC插件采集运行数据,量化对比调优前后效果。

5.4 常用调优参数

目标 参数示例
设置堆大小 -Xms4g -Xmx4g
新生代大小 -Xmn2g
堆外内存限制 -XX:MaxDirectMemorySize=512m
OOM时生成dump -XX:+HeapDumpOnOutOfMemoryError
打印GC停顿时间 -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -Xloggc:gc.log
打印安全点统计 -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1
安全点超时检测 -XX:+SafepointTimeout -XX:SafepointTimeoutDelay=2000
Windows GUI内存保持 -Dsun.awt.keepWorkingSetOnMinimize=true
去掉Survivor区(治标) -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0
最大GC停顿目标(G1) -XX:MaxGCPauseMillis=200

5.5 本章小结

  1. 调优前先确认"需要调优":以量化指标(RT、TP99、QPS、GC时间占比)为准
  2. 先排查业务代码,再调整JVM参数:大多数性能问题源于低效代码(如频繁调用外部命令、使用低效数据结构、不合理的集群同步)
  3. 善用监控工具:GC日志分析工具(GCViewer、GCEasy)、VisualVM + VisualGC、Arthas等
  4. 安全点问题容易被忽视:注意可数循环中可能导致的长时间停顿
  5. 32位系统的内存限制:进程最大内存通常为2GB,需综合考虑堆、直接内存、线程栈、Socket缓存等开销
相关推荐
ghie90902 小时前
基于学习的模型预测控制(LBMPC)MATLAB实现指南
开发语言·学习·matlab
ysa0510302 小时前
斐波那契上斐波那契【矩阵快速幂】
数据结构·c++·笔记·算法
倒酒小生2 小时前
4月7日算法学习小结
linux·服务器·学习
xinzheng新政2 小时前
Javascript·深入学习基础知识2
开发语言·javascript·学习
派大星~课堂2 小时前
【力扣-94.二叉树的中序遍历】Python笔记
笔记·python·leetcode
世人万千丶3 小时前
开源鸿蒙跨平台Flutter开发:儿童数理认知与神经塑性演化引擎_突触发生与工作记忆测绘架构
学习·flutter·华为·开源·harmonyos
ZhiqianXia3 小时前
PyTorch 学习笔记(10) : PyTorch torch.library
pytorch·笔记·学习
小陈phd3 小时前
多模态大模型学习笔记(三十一)—— 基于CCT(Compact Convolutional Transformers)实现中文车牌数据集微调
笔记·学习
zzh0813 小时前
MySQL故障排查与优化笔记
数据库·笔记·mysql