1, 内存分配相关调优
1.1, 最大与最小堆内存设置
Java 应用程序在运行时, 首先会被分配 -Xms 指定的内存大小, 并尽可能尝试在这个空间段内运行程序. 当 -Xms 指定的内存大小确实无法满足应用程序时, JVM 才会向操作系统中请更多的内存, 直到内存大小达到 -Xmx 指定的最大内存为止. 若超过 -Xmx 的值, 则抛出 OutOfMemoryError 异常. 如果 -Xms 的数值较小, 那么 JVM 为了保证系统尽可能地在指定内存范围内运行, 就会更加频繁地进行 GC 操作, 以释放失效的内存空间, 从而, 会增加 Minor GC 和 Full GC 的次数, 对系统性能产生一定的影响.
JVM 会试图将系统内存尽可能限制在 - Xms 中, 故当内存时间使用量触及 - Xms 指定的大小时, 会被触发 Full GC. 因此把 - Xms 值设置为 - Xms 时, 可以在系统运行初期减少 GC 的次数和耗时.
1.2, 设置新时代
参数 -Xmn 用于设置新生代的大小. 设置一个较大的新生代会减小老年代的大小, 这个参数对系统性能以及 GC 行为有很大的影响. 新生代的大小一般设置为整个堆空间的 1/4 到 1/3 左右.
在 Hot spot 虚拟机中,-XX : NewSize 用于设置新生代初始大小, -XX : MaxNewSize 用于设置新生代的最大值. 但通常情况下, 只设置 -Xmn 已经可以满足绝大部分应用的需要. 设置 -Xmn 的效果等同于设置了相同的 -XX : NewSize 和 -XX : MaxNewSize .
若设置不同的 - XX:NewSize 和 - XX:MaxNewSize 可能导致内存震荡, 从而产生不必要的系统开销.
1.3, 设置持久代
持久代 (方法区) 不属于堆的一部分. 在 Hot Spot 虚拟机中, 使用 -XX : MaxPermSize 可以设义持久代的最大值, 使用 -XX : PermSize 可以设置持久代的初始大小.
持久代的大小直接决定了系统可以支持多少个类定义和多少常量. 对于使用 CGLIB 或者 Javassist 等动态字节码生成工具的应用程序而言, 设置合理的持久代大小有助于维持系统稳定.
一般来说, MaxPermSize 设置为 64MB 已经可以满足绝大部分应用程序正常工作. 如果依然出现永久区溢出, 可以将 MaxPermSize 设置为 128MB . 这是两个很常用的永久区取值. 如果 128MB 依然不能满足应用程的需求, 那么对于大部分应用程序来说, 则应该考虑优化系统的设计, 减少动态类的产生.
1.4, 设置线程栈
在 JVM 中, 可以使用 -Xss 参数设置线程栈的大小.
在线程中, 进行局部变从分配, 函数调用时, 都需要在栈中开辟空间. 如果栈的空间分配太小, 那么线程在运行时, 可能没有足够的空间分配局部变量或者达不到足够的函数调用深度, 导致程序异常退出: 如果栈空间过大, 那么开设线程所需的内存成本就会上升, 系统所能支持的线程总数就会下降. 由于 Java 堆也是向操作系统申请内存空间的, 因此, 如果堆空间过大, 就会导致操作系统可用于线程栈的内存减少, 从而间接减少程序所能支持的线程数数量.
故如果系统确实需要大量线程并发执行, 那么设置一个较小的堆和较小的栈, 有助于提高系统所能承受的最大线程数.
1.5, 堆的分配比例
参数 -XX : SurvivorRatio 是用来设置新生代中, eden 空间和 s0 空间的比例关系. s0 和 s1 空间又分别称为 from 空间和 to 空间. 它们的大小是相同的, 职能也是一样的, 并在 Minor GC 后, 会互换角色.
-XX : SurvivorRatio = eden/s0 = eden/s1
参数 -XX:NewRatio = 老年代 / 新生代
-XX:SurvivorRatio 可以设置 eden 去和 survivor 区的比例.-XX:NewRatio 可以设置老年代与新时代的比例.
1.6, 堆分配参数总结
与 Java 应用程序堆内存相关的 JVM 参数有:
-Xms: 设置 Java 应用程序启动时的初始堆大小.
-Xmx: 设置 Java 应用程序能获得的最大堆大小.
-Xss : 设置线程栈的大小.
-XX : MinHeapFreeRatio : 设置堆空间最小空闲比例. 当堆空间的空闲内存小于这个数值时, JVM 便会扩展堆空间.
-XX:MaxHeapFreeRatio : 设置堆空间的最大空闲比例. 当堆空间的空闲内存大于这个数位时, 便会压缩堆空间小得到一个较小的堆.
-XX : NewSize : 设置新生代的人小.
-XX : NewRatio : 设置老年代与新生代的比例, 它等于老年代大小除以新生代大小.
-XX : SurvivorRatio: 新生代中 eden 区与 survivior 区的比例.
-XX : MaxPermSize : 设置最大的持久区大小.
-XX : PermSize : 设置永久区的初始值.
-XX : TargetSurvivorRatio : 设置 survivor 区的可使用率.当 survivor 区的空间使用率达到这个数值时, 会将对象送入老年代.
2, 常用调优方法
2.1, 将新对象预留在新生代
由于 FullGC 成本要远高于 MinorGC, 因此尽可能将对象分配在新生代是一项明智的做法. 虽然在大部分情况下, JVM 会尝试在 eden 区分配对象, 但由于空间紧张等问题, 很可能不得不将部分年轻对象提前向老年代压缩. 因此, 在 JVM 参数调优中, 可以为应用程序分配一个合理的新生代空间, 以最大限度避免对象直接进入老年代的情况.
通过设置一个较大的新生代预留新对象, 设置合理的 survivor 区并提高 survivor 区的使用率, 可以将年轻对象保存在新生代. 一般来说, 当 survivor 区的空间不够, 或者占用量达到 50 %时, 就会将对象进入老年代(不管对象的年龄有多大).
由于新生代垃圾回收速度高于老年代, 因此, 将年轻代对象预留在新生代有利于提高整体的 GC 效率.
2.2, 大对象进入老年代
虽然在大部分情况下, 将对象分配在新生代是合理的. 但是, 对于大对象, 这种做法却是值得商榷的. 因为大对象出现在新生代很可能扰乱新生代 GC , 并破坏新生代原有的对象结构. 因为尝试在新生代分配大对象, 很可能导致空间不足, 为了有足够的空间容纳大对象, JVM 不得不将新生代中的年轻对象挪到老年代. 因为大对象占用空间多, 所以, 可能需要移动大量小的年轻对象进入老年代. 这对 GC 来说足相当不利的.
基于以上原因, 可以将大对象直接分配到老年代, 保持新生代对象结构的完整性, 以提高 GC 的效率. 如果非常不幸的, 一个大对象同时又是短命的对象, 假设这种情况出现得比较频繁, 那么对 GC 来说将会是一场灾难. 原本应该用于存放永久对象的老年代, 被短命的对象塞满, 这也意味着对堆空间进行了洗牌, 扰乱了分代内存回收的基本思路. 因此, 软件开发过程中, 应该尽可能避免使用短命的大对象.
短命的大对象对垃圾回收是一场灾难. 目前没有一种特别好的回收方法处理这个问题. 因此, 开发人员应该尽量避免使用短命的大对象.
可以使用参数 - XX:PretenureSizeThreshold 设置大对象直接进入老年代的阈值. 当对象超大小超过这个值时, 直接进入老年代.
2.3, 设置对象进入老年代的年龄
在堆中, 每个对象都有自己的年龄. 一般情况下, 年轻对象存放在新生代, 年老对象存放在老年代. 为了做到这点, 虚拟机为每个对象都维护一个年龄.
如果对象在 eden 区, 经过一次 GC 后还存活, 则被移动到 survivior 区中, 对象年龄加 1 . 以后, 对象每经过一次 GC 依然存活的, 则年龄再加 1 . 当对象年龄达到阈值时, 就移入老年代, 成为老年对象.
这个阈值的最大值可以通过参数 -XX : MaxTenuringThreshold 来设置, 它的默值 15. 虽然 -XX : MaxTenuringThreshold 的值可能是 15 或者更大, 但这不意味着新对象非要达到这个年龄才能进入老年代. 事实上, 对象实际进入老年代的年龄是虚拟机在运行时根据内存使用情况动态计算的, 这个参数指定的是阈值年龄的最大值. 即, 实际晋升老年代年龄等于动态计算所得的年龄与 -XX : MaxTenuringThreshold 中较小的那个.
2.4, 稳定与震荡的堆大小
一般来说, 稳定的堆大小是对垃圾回收有利的. 获得一个稳定的堆大小的方法是使 -Xms 和 -Xmx 的大小一致, 即最大堆和最小堆(初始堆) 一样.如果这样设置, 系统在运行时, 堆大小是恒定的, 稳定的堆空间可以减少 GC 的次数. 因此, 很多服务端应用都会将最大堆和最小堆设置为相同的数值.
但是, 一个不稳定的堆也并不是毫无用处. 稳定的堆大小虽然可以减少 GC 次数, 但同时也增加了每次 GC 的时间. 让堆大小在一个区间中震荡, 在系统不需要使用大内存时, 压缩堆空问, 使 GC 应对一个较小的堆, 可以加快单次 GC 的速度. 基于这样的考虑, JVM 还提供了两个参数用于压缩和扩堆空间.,
-XX : MinHeapFreeRatio : 设置堆空间最小空闲比例, 默认是 40 . 当堆空间的空闲内存小于这个数值时, JVM 便会扩展堆空间.
-XX : MaxHeapFreeRatio : 设置堆空间的最大空闲比例, 默认是 70 .当堆空间的空闲内存大于这个数值时, 便会压缩堆空间, 得到一个较小的堆.
当 - Xms 和 - Xmx 相等时,-XX:MinHeapFreeRatio 和 - XX:MaxHeapFreeRatio 这两个参数是无效的.
3, 故障排查相关参数
3.1,JIT 编译参数
JVM 的 JIT ( Just-In-Time)编译器, 可以在运行时将字节码编译成本地代码, 从而提高函数的执行效率.-XX : CompileThreshold 为 JIT 编译的阈值, 当函数的调用次数超过 -XX : CompileThreshold 时, JIT 就将字节码编译成本地机器码. 在 client 模式下,-XX : CompileThreshold 的取值是 1500 ; 在 server 模式下, 取值是 10000 . JIT 编译完成后, JVM 便会用本地代码代替原来的字节码解释执行, 因此, 在系统的未来运行中, 这些时间是可以被赚回来的. JIT 编译会花费一定的时间, 为了能合理地设置 JIT 编译的阈值, 可以使用 -XX : + CITime 打印出 JIT 编译的耗时, 也可以使用 -XX : +PrintCompilation 打印出 JIT 编译的信息.
3.2, 对快照(堆 Dump)
在性能及故障排查过程中, 分析对快照是很重要的. 获得程序的堆快照有很多方法, 其中常用的方法为设置 - XX:+HeapDumpOnOutOfMemoryError 参数, 其使得程序在发生 OOM 时导出当前堆快照. 同时, 通过参数 - XX:+HeapDumpPath 可以指定堆快照的保存位置.
3.3, 错误处理
在系统发生 OOM 错误时, 虚拟机在错误发生时运行一段第三方脚本.
如当 OOM 时, 重置系统:-XX:OnOutOfMemoryError=c:\reset.bat
3.4, 取得 GC 信息
调优时获取 GC 信息是很重要的.
可以使用 - verbose:gc 或 - XX:PrintGC 输出 GC 日志信息;
如若需要获取详细信息, 使用 - XX:+PrintGCDetails;
如果需要在 GC 时打印详细的堆信息, 可以打开 - XX:+PrintHeapAtGC 开关. 一旦打开它, 那么每次 GC 时, 都将打印堆的使用情况. 包括 GC 前后的新生代, 老年代, 永久区的使用大小和使用率等.
如果需要查看 GC 与应用程序的互相执行的耗时, 可以使用 - XX:+PrintGCApplicationStoppedTime 和 - XX:+PrintGCApplicationConcurrentTime 参数. 他们分别显示程序在 GC 发生时停顿的时间和应用在 GC 停顿期间的执行时间.
可以使用 - Xloggc 参数指定 GC 日志的输出位置. 如:-Xloggc:C:\gc.log, 便于后期分析.
来源: http://www.jianshu.com/p/cbfc6f4f39b4