JVM 调优的本质:
并不是显著的提高系统性能,不是说你调了,性能就能提升几倍或者上十倍,
JVM 调优,主要调的是稳定。
如果你的系统出现了频繁的垃圾回收,这个 时候系统是不稳定的,所以需要我们来进行
JVM 调优,调整垃圾回收的频次。
GC 调优原则
1、 大多数的 java 应用不需要 GC 调优
2、 大部分需要 GC 调优的的,不是参数问题,是代码问题 [ 通过top -h 、jmap等一系列工具进行定位问题,然后进行 代码块优化
3、 在实际使用中,分析 GC 情况优化代码比优化 GC 参数要多得多;
4、 GC 调优是最后的手段
目的
GC 的时间够小
GC 的次数够少
发生 Full GC 的周期足够的长,时间合理,最好是不发生。
注:如果满足下面的指标,则一般不需要进行 GC:
- Minor GC 执行时间不到 50ms;
- Minor GC 执行不频繁,约 10 秒一次;
- Full GC 执行时间不到 1s;
- Full GC 执行频率不算频繁,不低于 10 分钟 1 次;
项目启动 GC 优化
1、 开启日志分析 -XX:+PrintGCDetails 发现有多次 GC 包括 FullGC
2、 调整 Metadata 空间 -XX:MetaspaceSize=64m
3、 减少 Minor gc 次数,增加参数 -Xms500m
4、 减少 Minor gc 次数,调整参数 -Xms1000m
5、 增加新生代比重,增加参数 -Xmn900m GC 减少至 1 次
6、 加大新生代,调整参数 -Xms2000m -Xmn1800m 还是避免不了 GC,没有必要调整这么大,资源浪费
推荐策略
1. 新生代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,新生代收集发生的频率也是最小 的.同时,减少到达老年代的对象.
吞吐量优先的应用:尽可能的设置大,可能到达 Gbit 的程度.因为对响应时间没有要求,垃圾收集可以并行进行,一般适合 8CPU 以上的应用.
避免设置过小.当新生代设置过小时会导致:1.MinorGC 次数更加频繁 2.可能导致 MinorGC 对象直接进入老年代,如果此时老年代满了,会触发 FullGC. 2. 老年代大小选择
2. 响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可 以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式; 如果堆大了,则需要较长的收集时间.最优化的方案,
一般需要参考以下数据获得:
并发垃圾收集信息、持久代并发收集次数、传统 GC 信息、花在新生代和老年代回收上的时间比例。
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而 老年代尽存放长期存活对象
GC 调优是个很复杂、很细致的过程,要根据实际情况调整,不同的机器、不同的应用、不同的性能要求调优的手段都是不同的,king 老师也无法告诉大 家全部,即使是 jvm 参数也是如此,比如说性能有关的操作系统工具,和操作系统本身相关的所谓大页机制,都需要大家平时去积累,去观察,去实践, king 老师在这个专题上告诉大家的除了各种 java 虚拟机基础知识和内部原理,也告诉大家一个性能优化的一个基本思路和着手的方向。
日志分析
1,监控 GC 的状态
使用各种 JVM 工具,查看当前日志,分析当前 JVM 参数设置,并且分析当前堆内存快照和 gc 日志,根据实际的各区域内存划分和 GC 执行时间,觉得是 否进行优化;
2,分析结果,判断是否需要优化
如果各项参数设置合理,系统没有超时日志出现,GC 频率不高,GC 耗时不高,那么没有必要进行 GC 优化;如果 GC 时间超过 1-3 秒,或者频繁 GC,则 必须优化;
3,调整 GC 类型和内存分配
如果内存分配过大或过小,或者采用的 GC 收集器比较慢,则应该优先调整这些参数,并且先找 1 台或几台机器进行 beta,然后比较优化过的机器和没有 优化的机器的性能对比,并有针对性的做出最后选择;
4,不断的分析和调整
通过不断的试验和试错,分析并找到最合适的参数
5,全面应用参数
如果找到了最合适的参数,则将这些参数应用到所有服务器,并进行后续跟踪。
3.设置新生代
参数-Xmn可用于设置新生代大小。设置一个较大的新生代会减小老年代的大小,这个参数对系统性能以及GC行为有很大的影响。新生代的大小一般设置为整个堆空间的1/4到1/3左右。
4.设置持久代
持久代(方法区)不属于堆的一部分,在Hot Spot虚拟机中,使用-XX:MaxPermSize可以设置持久代的最大值,使用-XX:PermSize可以设置持久代的初始大小。
持久代的大小直接决定了系统可以支持多少各类定义和多少常量,设置合理的持久代大小有助于维持系统的稳定。
系统支持的最大类数量,与MaxPermSize成正比。一般来说,MaxPermSize设置为64MB已经可以满足绝大部分应用程序正常工作。如果依然出现永久区溢出,可以将MaxPermSize设置为128MB。这是两个常用的永久区取值。如果128MB依然不能满足应用程序需求,那么对于大部分应用程序而言,应该考虑优化系统设计,减少动态类的产生,或者利用GC回收部分驻扎在永久区的无用类信息,以使系统健康运行。
5.设置线程栈
线程栈是线程的一块私有空间,在JVM中,可以使用-Xss参数设置线程栈的大小。
在线程中进行局部变量分配、函数调用时,都需要在栈中开辟空间。如果栈空间分配太小,那么线程在运行时,可能会因为没有足够的空间分配局部变量或者达不到足够的函数调用深度,导致程序异常退出。如果栈空间太大,那么开设线程所需的内存成本就会上升,系统所能支持的线程总数就会下降。
因为Java堆也是向操作系统申请内存空间,因此如果堆空间太大,就会导致操作系统可用于线程栈的空间减少,从而间接减少程序所能支持的线程数量。
以下代码会尝试尽可能的开设线程,并且在线程数量饱和时,打印已经开设的线程数量:
public static class MyThread extends Thread{
@Override
public void run(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]){
int i=0;
try{
for( i=0;i<10000;i++){
new MyThread().start();
}
}catch(OutOfMemoryError e){
System.out.println("count thread is "+i);
e.printStackTrace();
}
}
首先使用-Xss1M运行程序,即设置每个线程拥有1MB的栈空间,在我的计算机上,总共可以开设线程1168个。当使用-Xss10M运行程序时,公共可以137个线程。
如果尝试在JVM参数中指定堆大小,则会发现系统所支持的线程数和对大小还有关系:
-Xss1M -Xss20M
-Xms100m -Xms100M 1170 66
-Xms300m -Xms300M 1139 64
-Xms500m -Xms500M 998 55
-Xms700m -Xms700M 852 44
当系统空间不够而无法创建新的线程时,会抛出OutOfMemoryError错误。但这并不是因为堆内存不够而造成的OOM,在这种情况下,应该尝试减少堆内存,以换取更多的系统空间来解决这个问题。
综上,如果系统确实需要大量线程并发执行,那么设置一个较小的堆和较小的栈有助于提高系统所能承受的最大线程数。