今天接着补全上篇博文。
三 垃圾收集策略配置
3.1 吞吐量优先
吞吐量是指GC的时间与运行总时间的比值,比如系统运行了100分钟,而GC占用了一分钟,那么吞吐量就是99%,吞吐量优先一般运用于对响应性要求不高的场合,比如web应用,因为网络传输本来就有延迟的问题,GC造成的短暂的暂停使得用户以为是网络阻塞所致。
吞吐量优先可以通过-XX:GCTimeRatio来指定。
当通过-XX:GCTimeRatio不能满足系统的要求以后,我们可以更加细致的来对JVM进行调优。
首先因为要求高吞吐量,这样就需要一个较大的Young generation,此时就需要引入“
Parallel
scavenging
Collector”
,
可以通过参数:-XX:UseParallelGC来配置。
java -server -Xms3072m -Xmx3072m -XX:NewSize=2560m -XX:MaxNewSize=2560 XX:SurvivorRatio=2 -XX:+UseParallelGC
当年轻代使用了"Parallel scavenge collector"
后,老生代就不能使用"CMS"GC了,在JDK1.6之前,此时老生代只能采用串行收集,而JDK1.6引入了并行版本的老生代收集器,可以用参数
-XX:U
seParallelOldGC
来配置
。
3.1.1 控制并行的线程数
缺省情况下,Parallel scavenging Collector 会开启与cpu数量相同的线程进行并行的收集,但是也可以调节并行的线程数。假如你想用4个并行的线程去收集Young generation的话,那么就可以配置-XX:ParallelGCThreads=4,此时JVM的配置参数如下:
java -server -Xms3072m -Xmx3072m -XX:NewSize=2560m -XX:MaxNewSize=2560 XX:SurvivorRatio=2 -XX:+UseParallelGC
-XX:ParallelGCThreads=4
3.1.2 自动调节新生代
在采用了"Parallel scavenge collector"后,此GC会根据运行时的情况自动调节survivor ratio来使得性能最优,因此"Parallel scavenge collector"应该总是开启此参数。
此时JVM的参数配置如下:
java -server -Xms3072m -Xmx3072m -XX:+UseParallelGC
-XX:ParallelGCThreads=4
-XX:+UseAdaptiveSizePolicy
3.2 响应时间优先
响应时间优先是指GC每次运行的时间不能太久,这种情况一般使用与对及时性要求很高的系统,比如股票系统等。
响应时间优先可以通过参数-XX:MaxGCPauseMillis来配置,配置以后JVM将会自动调节年轻代,老生代的内存分配来满足参数设置。
在一般情况下,JVM的默认配置就可以满足要求,只有默认配置不能满足系统的要求时候,才会根据具体的情况来对JVM进行性能调优。如果采用默认的配置不能满足系统的要求,那么此时就可以自己动手来调节。
此时"Young generation"可以采用"Parallel copying collector",而"Old generation"则可以采用"Concurrent Collector",
举个例子来说,以下参数设置了新生代用
Parallel Copying Collector
,
老生代采用CMS收集器
。
java -server -Xms512m -Xmx512m
-XX:NewSize=64m -XX:MaxNewSize=64m
-XX:SurvivorRatio=2
-
X
X:+UseConcMarkSweepGC -XX:+UseParNewGC
此时需要注意两个问题:
1
如果没有指定
-XX:+UseParNewGC,则采用默认的非并行版本的
copy collector.
2 如果在一个单CPU的系统上设置了
-XX:+UseParNewGC ,
则默认还是采用缺省的copy collector.
3.2.1 控制并行的线程数
默认情况下,Parallel copy collector启动和CPU数量一样的线程,也可以通过参数-XX:ParallelGCThreads来指定,比如你想用3个线程去进行并发的复制收集,那么可以改变上述参数如下:
java -server -Xms512m -Xmx512m -XX:NewSize=64m
-XX:MaxNewSize=64m
-XX:SurvivorRatio=2
-XX:P
arallelGCThreads=4
-
X
X:+UseConcMarkSweepGC
-XX:+UseParNewGC
3.2.2 控制并发收集的临界值
默认情况下,CMS gc在"old generation"空间占用率高于68%的时候,就会进行垃圾收集,而如果想控制收集的临界值,可以通过参数:-XX:CMSInitiatingOccupancyFraction来控制,比如改变上述的JVM配置如下:
java -server -Xms512m -Xmx512m -XX:NewSize=64m
-XX:MaxNewSize=64m
-XX:SurvivorRatio=2
-XX:P
arallelGCThreads=4
-
X
X:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:CMSIn
itiatingOccupancyFraction=35
四 GC触发以及常见的内存错误
4.1 Minor GC的触发
Minor GC主要负责收集Young Generation,Minor GC一般在新生代不够用的情况下触发,比如我们一次性创建了很多对象等。
List<byte[]> buffer = new ArrayList<byte[]>();
for(int i=0;i<8*1024;i++){
buffer.add(new byte[1024]);
}
以上代码通过一个字节数组的List模拟触发Minor gc,设置JVM参数如下:
-verbose:gc -Xmn10M -Xms64M -Xmx64M -XX:+PrintGC
设置以上参数以后,因为-Xmn=10M,默认-XX:SurvivorRatio=8 ,则eden的空间大小为8M,当eden对象大小超过8M的时候就会触发Minor gc.
运行的结果如下:
[GC 8192K->8030K(64512K), 0.0243391 secs]
从运行结果可以看出,gc前和gc后的eden区的占用情况,需要注意的是括号里(64512)这个数值时63M,它不包括一块Survivor 空间。
这里需要注意的一点就是,如果创建的对象大于eden的大小,那么将不会通过Survivor空间复制,直接转移到old generation.
调整以上代码如下:
List<byte[]> buffer = new ArrayList<byte[]>();
buffer.add(new byte[8*1024*1024]);
通过同样的JVM参数运行,则发现不会触发Minor gc,这是因为对象超过了eden的大小,从而直接分配到了Old generation.
4.2 Major GC的触发
4.2.1 Old Generation空间满或者接近某一个比例
Old generation 空间满是因为Young generation提升到Old generation的对象+Old generation的本来的大小已经接近或者超过了Old generation的大小。对于CMS GC,当Old generation空间使用率接近某一个比例,可以通过参数-XX:CMS InitialingOccupancyFraction,此参数表示Old generation的使用率,默认为68%。
Young generation对象提升到Old generation对象有以下三种情况:
Ø
分配的对象大于eden空间的大小
Ø
在Young generation代中经过了-XX:MaxTenuringThreshold次复制任然存活的对象
Ø
Minor gc的时候,放不进to survivor的对象
当Major GC以后,如果还没有足够的空间可以用的话,此时就会抛出java.lang.OutOfMemory:java heap space,当出现此错误的时候,说明可能存在内存泄露现象的,这时候就需要我们对程序进行检查看看什么地方存在内存泄露的。
我们可以通过以下代码来模拟一下java.lang.OutOfMemory:java heap space的发生:
List<byte[]> buffer = new ArrayList<byte[]>();
buffer.add(new byte[10*1024*1024]);
以上代码分配了一个10M的字节数组,我们通过以下的参数运行:
-verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC
以上参数指定Young generation的空间大小为10M,Old generation空间大小为10M。
运行结果如下:
[GC 327K->134K(19456K), 0.0056516 secs]
[Full GC 134K->134K(19456K), 0.0178891 secs]
[Full GC 134K->131K(19456K), 0.0141412 secs]
Exception in thread "main" java.lang.OutOfMemoryError:
Java heap space
at Test.main(Test.java:30)
从运行结果可以看出,JVM进行了一次Minor gc和两次的Major gc,从Major gc的输出可以看出,gc以后old区使用率为134K,而字节数组为10M,加起来大于了old generation的空间,所以抛出了异常,如果调整-Xms21M,-Xmx21M,那么就不会触发gc操作也不会出现异常了。
4.2.2 Perm Generation 空间满
Perm Generation空间主要存放Class对象,Field,Method对象,当一次性加载太多的类或者在热部署以后不卸载类的情况(比如在Jboss服务器中,如果经常热部署一些应用就会出现Perm 空间溢出)就会造成Perm Generation被占满,此时就会出现:
java.lang.OutOfMemory:PermGen space,在出现此异常的时候,如果是因为热部署引起的,我们重新启动AS就可以了,如果是因为加载的类太多,此时可以通过-XX:PermSize和-XX:MaxPermSize调整。
4.3 常见内存错误分析
4.3.1 StackOverflowError
java.lang.StackOverflowError错误表示JVM栈溢出,出现这个错误的原因一般都是递归的层次太深,或者无限的递归造成的。出现这种错误的时候首先要对应用程序进行检查,看看是那些代码造成了栈溢出,如果是递归造成的可以改为迭代方式实现。
JVM同样也提供了一个参数来让我们调节运行时栈空间的大小。
-XX:Xss=256K
表示栈空间最大为
256
K.我们也可以调大,但是建议不要对此参数进行调节。
4.3.2 OutOfMemoryError: Java heap space.
java.lang.OutOfMemoryError: Java heap space这个错误表示JVM的新生代和老生代的内存不足。出现这个错误说明应用程序出现了内存溢出或者程序所需要的内存大于JVM的内存设置了。
遇到这个问题的时候,首先我们可以调节JVM的Heap内存的大小,具体可以通过
-Xmx -Xms来进行设置,如果设置大以后还是会出现内存溢出,那么说明应用程序本身存在内存泄露,这个时候就需要我们对应用程序进行检查,找出导致内存泄露的地方,然后修正。
4.3.3 OutOfMemory:PermGen space
java.lang.OutOfMemory:PermGen space错误是由Perm space空间不足。一般出现这个错误是由加载了太多的类或者大量使用了动态代理造成的。如果出现了这个错误,我们可以将Perm空间调大一点。
-XX:PermSize=
16
M -XX:MaxPermSize=
64
M
参考资料
转载于:https://blog.51cto.com/yuquan/361465