架构师修炼之道

知识日新月异,唯有保持同步迭代,才能适应美好的未来!

0%

Java虚拟机(JVM)调优思路

调什么

内存方面

  1. JVM需要的内存总大小
  2. 各块内存分配,新生代、老年代、存活区
  3. 选择合适的垃圾回收算法、控制GC停顿次数和时间
  4. 解决内存泄露的问题,辅助代码优化
  5. 内存热点:检查哪些对象在系统中数量最大,辅助代码优化

线程方面

  1. 死锁检查,辅助代码优化
  2. Dump线程详细信息:查看线程内部运行情况,查找竞争线程,辅助代码优化
  3. CPU热点:检查系统哪些方法占用的大量CPU时间,辅助代码优化

如何调

  1. 监控JVM的状态,主要是内存、线程、代码、I/O几个部分
  2. 分析结果,判断是否需要优化
  3. 调整GC类型和内存分配;修改并优化代码
  4. 不断的重复分析和调整,直至找到优化的平衡点

JVM调优的目标

  1. GC的时间足够的小
  2. GC的次数足够的少
  3. 将转移到老年代的对象数量降低到最小
  4. 减少full GC的执行时间
  5. 发生Full GC的间隔足够的长

JVM调优冷思考

  1. 多数的Java应用不需要在服务器上进行GC优化
  2. 多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题
  3. 在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合)
  4. GC优化是到最后不得已才采用的手段
  5. 在实际使用中,分析GC情况优化代码比优化GC参数要多得多
  6. 如下情况通常不用优化:
  • Minor GC执行时间不到50ms
  • Minor GC执行不频繁,约10秒一次
  • Full GC执行时间不到1s
  • Full GC执行频率不算频繁,不低于10分钟1次

常见调优策略

  1. 减少创建对象的数量
  2. 减少使用全局变量和大对象
  3. 调整新生代的大小到最合适
  4. 设置老年代的大小为最合适
  5. 选择合适的GC收集器
  6. 将转移到老年代的对象数量降到最少
  7. 减少Full GC的执行时间

调优经验

  1. 要注意32位和64位的区别,通常32位的仅支持2-3g左右。
  2. 要注意client模式和Server模式的选择:client runtime是快速启动,更小的内存占用以及快速代码(机器码)生成的JIT编译器。server runtime有更复杂的代码生成优化,作为服务型应用更为靠谱
  3. 要想GC时间小必须要一个更小的堆;而要保证GC次数足够少,又必须保证一个更大的堆,这两个是有冲突的,只能取其平衡。
  4. 针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值。
  5. 新生代和老年代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以通过-XX:newSize -XX:MaxNewSize来设置其绝对大小,同样,为了防止新生的堆收缩,通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。
  6. 更大的新生代必然导致更小的老年代,大的新生代会延长普通GC的周期,但会增加每次GC的时间;小的老年代会导致更频繁的Full GC;更小的新生代必然导致更大老年代,小的新生代会导致普通GC很频繁,但每次的GC时间会更短;大的老年代会减少Full GC的频率。
  7. 如果应用存在大量的临时对象,应该选择更大的新生代;如果存在相对较多的持久对象,老年代应该适当增大。在抉择时应该本着Full GC尽量少的原则,让老年代尽量缓存常用对象,JVM的默认比例1:2也是这个道理。
  8. 通过观察应用一段时间,看其在峰值时老年代会占多少内存,在不影响Full GC的前提下,根据实际情况加大新生代,比如可以把比例控制在1:1。但应该给老年代至少预留1/3的增长空间。
  9. 线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程。

内存泄漏

一:内存泄露导致系统崩溃前的一些现象

  1. 每次垃圾回收的时间越来越长,FullGC的时间也延长到好几秒
  2. FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC
  3. 老年代的内存越来越大,并且每次FullGC后年老代没有内存被释放,慢慢的系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值。

二:老年代堆空间被占满的情况

  1. 这是非常典型的内存泄漏的垃圾回收情况,通常监控图上所有峰值部分都是一次垃圾回收点,所有谷底部分表示是一次垃圾回收后剩余的内存。连接所有谷底的点,可以发现一条由底到高的线,这说明随时间的推移,系统的堆空间被不断占满,最终会占满整个堆空间。
  2. 这种情况的解决方式:一般就是根据垃圾回收前后情况对比,同时根据对象引用情况分析,基本都可以找到泄漏点。

三:堆栈溢出的情况

  1. 通常抛出java.lang.StackOverflowError例外
  2. 这个一般就是递归调用没退出,或者循环调用造成