吞吐量是指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 来配置 。
缺省情况下,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
在采用了"Parallel scavenge collector" 后, 此 GC 会根据运行 时的情况自动调节 survivor ratio 来 使得性能最优,因此 "Parallel scavenge collector" 应 该总是开启此参数。
此时JVM 的参数配置如下:
java -server -Xms3072m -Xmx3072m -XX:+UseParallelGC -XX:ParallelGCThreads=4 -XX:+UseAdaptiveSizePolicy
响应时间优先是指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.
默认情况下,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
默认情况下,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
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.
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 操 作也不会出现异常了。
Perm Generation空 间主要存放 Class 对 象, Field,Method 对 象,当一次性加载太多的类或者在热部署以后不卸载类的情况(比如在 Jboss 服务器中,如果经常热部署一些应用就会出现 Perm 空间溢出)就会造成 Perm Generation 被占满,此 时就会出现:
java.lang.OutOfMemory:PermGen space,在 出现此异常的时候,如果是因为热部署引起的,我们重新启动 AS 就可以了,如果是因为加载的类太多,此时可以通过 -XX:PermSize 和 -XX:MaxPermSize 调整。
java.lang.StackOverflowError错 误表示 JVM 栈溢 出,出现这个错误的原因一般都是递归的层次太深,或者无限的递归造成的。出现这种错误的时候首先要对应用程序进行检查,看看是那些代码造成了栈溢出,如果 是递归造成的可以改为迭代方式实现。
JVM同 样也提供了一个参数来让我们调节运行时栈空间的大小。 -XX:Xss=256K 表示栈空间最大为 256 K.我们也可以调大,但是建议不 要对此参数进行调节。
java.lang.OutOfMemoryError: Java heap space这 个错误表示 JVM 的 新生代和老生代的内存不足。出现这个错误说明应用程序出现了内存溢出或者程序所需要的内存大于 JVM 的内存设置了。
遇到这个问题的时 候,首先我们可以调节JVM 的 Heap 内存的大小,具体可以通过 -Xmx -Xms来进行设 置,如果设置大以后还是会出现内存溢出,那么说明应用程序本身存在内存泄露,这个时候就需要我们对应用程序进行检查,找出导致内存泄露的地方,然后修正。
java.lang.OutOfMemory:PermGen space错 误是由 Perm space 空 间不足。一般出现这个错误是由加载了太多的类或者大量使用了动态代理造成的。如果出现了这个错误,我们可以将 Perm 空间调大一点。
-XX:PermSize= 16 M -XX:MaxPermSize= 64 M
参考资料
1 http://developers.sun.com/mobility/midp/articles/garbage/
2 http://developers.sun.com/mobility/midp/articles/garbagecollection2/
3 http://blogs.sun.com/watt/resource/jvm-options-list.html
4 http://java.sun.com/developer/technicalArticles/Programming/turbo/
5 http://www.ibm.com/developerworks/library/j-jtp10283/index.html?S_TACT=105AGX52&S_CMP=cn-a-j
6 http://www.ibm.com/developerworks/library/j-jtp11253/index.html?S_TACT=105AGX52&S_CMP=cn-a-j