尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JVM调优实战:从原理到性能优化

JVM调优实战:从原理到性能优化 1. 为什么我们需要JVM调优第一次在生产环境遇到JVM性能问题时我盯着监控面板上那条不断攀升的内存曲线手心全是汗。那是一个普通的周二下午我们的订单系统突然开始出现间歇性卡顿而当时距离618大促只有两周时间。这就是我真正开始系统学习JVM调优的契机——当理论遇上现实的火花。JVM调优本质上是在做三件事第一是让应用跑得更快吞吐量第二是让应用停顿更少延迟第三是让应用更稳定避免OOM。听起来简单但每个目标背后都有一整本故事书那么厚的知识点。比如你调整了新生代大小可能改善了GC频率却导致单次GC时间变长你增加了堆内存可能缓解了OOM却引来了更长的Full GC。重要提示在没有明确性能问题指标前就开始调优就像在没有诊断报告时就开药——可能适得其反。2. JVM内存模型调优的地基2.1 运行时数据区详解JVM内存模型就像一栋精心设计的公寓楼每个区域都有特定用途。堆内存是最大的共享空间存放所有对象实例方法区存储类信息、常量等元数据虚拟机栈、本地方法栈和程序计数器则是线程私有的工作空间。最常出问题的就是堆内存它又分为新生代Young Generation新对象的摇篮分为Eden区和两个Survivor区老年代Old Generation长期存活对象的养老院元空间MetaspaceJDK8取代永久代的存在// 通过代码验证内存分配 public class MemoryAllocation { public static void main(String[] args) { byte[] allocation1 new byte[28000*1024]; // 直接进入老年代 } }2.2 指针碰撞与空闲列表当我们需要在堆中创建新对象时JVM有两种内存分配策略指针碰撞Bump the Pointer适用于规整的内存布局简单移动指针即可空闲列表Free List适用于不连续内存需要维护可用内存块列表选择哪种方式取决于垃圾收集器的选择。比如Serial、ParNew等收集器采用指针碰撞而CMS这类基于标记-清除算法的收集器则使用空闲列表。3. 垃圾收集器JVM的清洁工团队3.1 主流收集器对比下表是常见收集器的特性对比收集器算法适用区域线程特点Serial复制新生代单线程简单高效适合客户端ParNew复制新生代多线程Serial的多线程版Parallel Scavenge复制新生代多线程吞吐量优先Serial Old标记-整理老年代单线程Serial的老年代版Parallel Old标记-整理老年代多线程Parallel Scavenge的老年代搭档CMS标记-清除老年代多线程低延迟优先G1分区算法全堆多线程平衡型JDK9默认ZGC染色指针全堆多线程超低延迟3.2 CMS收集器的三色标记CMS收集器的工作过程就像垃圾分类初始标记Stop The World快速标记GC Roots直接关联对象并发标记与用户线程并行遍历对象图重新标记Stop The World修正并发标记期间的变动并发清除清理垃圾对象# 启用CMS的JVM参数示例 -XX:UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction70 -XX:UseCMSInitiatingOccupancyOnly4. 实战调优从参数到监控4.1 基础参数设置一个电商应用的典型配置-Xms4g -Xmx4g # 堆大小固定避免动态调整开销 -XX:NewRatio2 # 新生代与老年代比例 -XX:SurvivorRatio8 # Eden与Survivor区比例 -XX:UseG1GC # 使用G1收集器 -XX:MaxGCPauseMillis200 # 目标暂停时间 -XX:InitiatingHeapOccupancyPercent45 # 触发并发GC的堆占用率4.2 监控工具链jps查看Java进程jps -lvmjstat实时监控GC情况jstat -gcutil pid 1000 10jmap堆内存分析jmap -histo:live pid | head -20VisualVM图形化分析工具Arthas线上诊断神器5. 常见问题排查手册5.1 CPU飙升问题排查步骤top命令找到高CPU进程top -Hp 定位高CPU线程printf %x\n 转换线程ID为16进制jstack | grep -A 20 查看线程栈5.2 OOM问题不同类型OOM的应对策略Java heap space增加堆大小或查找内存泄漏GC overhead limit exceeded优化GC策略或代码Metaspace调整-XX:MaxMetaspaceSizeUnable to create new native thread减少线程数或调整系统限制6. 高级调优技巧6.1 逃逸分析与栈上分配JVM会分析对象作用域对于未逃逸出方法外的对象可能直接在栈上分配减少GC压力。可以通过-XX:DoEscapeAnalysis开启默认开启。// 适合栈上分配的例子 public void method() { User user new User(); // 未逃逸对象 user.setName(test); System.out.println(user.getName()); }6.2 大对象直接进入老年代通过-XX:PretenureSizeThreshold参数可以设置大对象的阈值仅对Serial和ParNew收集器有效。-XX:PretenureSizeThreshold3145728 # 3MB以上的对象直接分配在老年代7. 时区问题那些年踩过的坑在JDBC连接MySQL时时区不一致会导致时间字段出现令人困惑的偏差。解决方法// JDBC连接字符串添加时区参数 jdbc:mysql://localhost:3306/db?serverTimezoneAsia/Shanghai同时确保JVM时区配置正确-Duser.timezoneGMT088. 我的调优心得经过多次线上事故的洗礼我总结了三条黄金法则调优前先收集足够的数据指标没有监控就不要调优每次只改变一个参数并记录前后对比生产环境变更要走灰度发布准备好回滚方案最深刻的教训来自一次盲目增加新生代大小的操作。原本想减少Minor GC频率结果导致单次GC时间翻倍反而让接口超时增多。后来通过G1收集器的Region设计完美解决了这个问题——这就是为什么理解原理比记住参数更重要。
返回列表