开这帖的目的是想让大家了解到,所谓“标准参数”是件很微妙的事情。确实有许多前辈经过多年开发积累下了许多有用的调优经验,但向他们问“标准参数”并照单全收是件危险的事情。
前辈们提供的“标准参数”或许适用于他们的应用场景,他们或许也知道这些参数里隐含的陷阱;但听众却不一定知道各种参数背后的缘由。
原则上说,在生产环境使用非标准参数(这里指的是在各JDK/JRE实现特有的、相互之间不通用的参数)应该尽量避免。这些参数与具体实现密切相关,不是光了解很抽象的“JVM原理”就足以理解的;即便在同一系列的JDK/JRE实现中,非标准参数也不保证在各版本间有一样的作用;而且许多人只看名字就猜想参数的左右,做“调优”却适得其反。
非标准参数的默认值在不同版本间或许会悄然发生变化。这些变化的背后多半有合理的理由。设了一大堆非标准参数、不明就里的同学在升级JDK/JRE的时候也容易掉坑里。
下面用Oracle/Sun JDK 6来举几个例子。
======================================================================
1、-XX:+DisableExplicitGC 与 NIO的direct memory
很多人都见过JVM调优建议里使用这个参数,对吧?但是为什么要用它,什么时候应该用而什么时候用了会掉坑里呢?
首先要了解的是这个参数的作用。在Oracle/Sun JDK这个具体实现上,System.gc()的默认效果是引发一次stop-the-world的full GC,对整个GC堆做收集。有几个参数可以改变默认行为,之前发过一帖简单描述过,这里就不重复了。关键点是,用了-XX:+DisableExplicitGC参数后,System.gc()的调用就会变成一个空调用,完全不会触发任何GC(但是“函数调用”本身的开销还是存在的哦~)。
为啥要用这个参数呢?最主要的原因是为了防止某些手贱的同学在代码里到处写System.gc()的调用而干扰了程序的正常运行吧。有些应用程序本来可能正常跑一天也不会出一次full GC,但就是因为有人在代码里调用了System.gc()而不得不间歇性被暂停。也有些时候这些调用是在某些库或框架里写的,改不了它们的代码但又不想被这些调用干扰也会用这参数。
OK。看起来这参数应该总是开着嘛。有啥坑呢?
其中一种情况是下述三个条件同时满足时会发生的:
1、应用本身在GC堆内的对象行为良好,正常情况下很久都不发生full GC;
2、应用大量使用了NIO的direct memory,经常、反复的申请DirectByteBuffer
3、使用了-XX:+DisableExplicitGC
能观察到的现象是:
Log代码
1.java.lang.OutOfMemoryError: Direct buffer memory
2. at java.nio.Bits.reserveMemory(Bits.java:633)
3. at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
4. at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
5....
做个简单的例子来演示这现象:
Java代码
1.import java.nio.*;
2.
3.public class DisableExplicitGCDemo {
4. public static void main(String[] args) {
5. for (int i = 0; i < 100000; i++) {
6. ByteBuffer.allocateDirect(128);
7. }
8. System.out.println("Done");
9. }
10.}
然后编译、运行之:
Command prompt代码
1.$ java -version
2.java version "1.6.0_25"
3.Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
4.Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)
5.$ javac DisableExplicitGCDemo.java
6.$ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC -XX:+DisableExplicitGC DisableExplicitGCDemo
7.Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
8. at java.nio.Bits.reserveMemory(Bits.java:633)
9. at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
10. at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
11. at DisableExplicitGCDemo.main(DisableExplicitGCDemo.java:6)
12.$ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC DisableExplicitGCDemo
13.[GC 10996K->10480K(120704K), 0.0433980 secs]
14.[Full GC 10480K->10415K(120704K), 0.0359420 secs]
15.Done
可以看到,同样的程序,不带-XX:+DisableExplicitGC时能正常完成运行,而带上这个参数后却出现了OOM。
例子里用-XX:MaxDirectMemorySize=10m限制了DirectByteBuffer能分配的空间的限额,以便问题更容易展现出来。不用这个参数就得多跑一会儿了。
在这个例子里,main()里的循环不断申请DirectByteBuffer但并没有引用、使用它们,所以这些DirectByteBuffer应该刚创建出来就已经满足被GC的条件,等下次GC运行的时候就应该可以被回收。
实际上却没这么简单。DirectByteBuffer是种典型的“冰山”对象,也就是说它的Java对象虽然很小很无辜,但它背后却会关联着一定量的native memory资源,而这些资源并不在GC的控制之下,需要自己注意控制好。对JVM如何使用native memory不熟悉的同学可以参考去年JavaOne上IBM的一个演讲,“Where Does All the Native Memory Go”。
Oracle/Sun JDK的实现里,DirectByteBuffer有几处值得注意的地方。
1、DirectByteBuffer没有finalizer,它的native memory的清理工作是通过sun.misc.Cleaner自动完成的。
2、sun.misc.Cleaner是一种基于PhantomReference的清理工具,比普通的finalizer轻量些。对PhantomReference不熟悉的同学请参考Bob Lee最近几年在JavaOne上做的演讲,"The Ghost in the Virtual Machine: A Reference to References"。今年的JavaOne上他也讲了同一个主题,内容比前几年的稍微更新了些。PPT可以从链接里的页面下载到。
Java代码
1./**
2. * General-purpose phantom-reference-based cleaners.
3. *
4. * <p> Cleaners are a lightweight and more robust alternative to finalization.
5. * They are lightweight because they are not created by the VM and thus do not
6. * require a JNI upcall to be created, and because their cleanup code is
7. * invoked directly by the reference-handler thread rather than by the
8. * finalizer thread. They are more robust because they use phantom references,
9. * the weakest type of reference object, thereby avoiding the nasty ordering
10. * problems inherent to finalization.
11. *
12. * <p> A cleaner tracks a referent object and encapsulates a thunk of arbitrary
13. * cleanup code. Some time after the GC detects that a cleaner's referent has
14. * become phantom-reachable, the reference-handler thread will run the cleaner.
15. * Cleaners may also be invoked directly; they are thread safe and ensure that
16. * they run their thunks at most once.
17. *
18. * <p> Cleaners are not a replacement for finalization. They should be used
19. * only when the cleanup code is extremely simple and straightforward.
20. * Nontrivial cleaners are inadvisable since they risk blocking the
21. * reference-handler thread and delaying further cleanup and finalization.
22. *
23. *
24. * @author Mark Reinhold
25. * @version %I%, %E%
26. */
重点是这两句:"A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner."
Oracle/Sun JDK 6中的HotSpot VM只会在年老代GC(full GC/major GC或者concurrent GC都算)的时候才会做reference processing,而在young GC/minor GC时不做。
也就是说,做full GC的话会做reference processing,进而能触发Cleaner对已死的DirectByteBuffer对象做清理工作。而如果很长一段时间里没做过GC或者只做了young GC的话则不会触发Cleaner的工作,那么就可能让本来已经死了的DirectByteBuffer关联的native memory得不到及时释放。
3、为DirectByteBuffer分配空间过程中会显式调用System.gc(),以期通过full GC来强迫已经无用的DirectByteBuffer对象释放掉它们关联的native memory:
Java代码
1.// These methods should be called whenever direct memory is allocated or
2.// freed. They allow the user to control the amount of direct memory
3.// which a process may access. All sizes are specified in bytes.
4.static void reserveMemory(long size) {
5.
6. synchronized (Bits.class) {
7. if (!memoryLimitSet && VM.isBooted()) {
8. maxMemory = VM.maxDirectMemory();
9. memoryLimitSet = true;
10. }
11. if (size <= maxMemory - reservedMemory) {
12. reservedMemory += size;
13. return;
14. }
15. }
16.
17. System.gc();
18. try {
19. Thread.sleep(100);
20. } catch (InterruptedException x) {
21. // Restore interrupt status
22. Thread.currentThread().interrupt();
23. }
24. synchronized (Bits.class) {
25. if (reservedMemory + size > maxMemory)
26. throw new OutOfMemoryError("Direct buffer memory");
27. reservedMemory += size;
28. }
29.
30.}
这几个实现特征使得Oracle/Sun JDK 6依赖于System.gc()触发GC来保证DirectByteMemory的清理工作能及时完成。如果打开了-XX:+DisableExplicitGC,清理工作就可能得不到及时完成,于是就有机会见到direct memory的OOM,也就是上面的例子演示的情况。我们这边在实际生产环境中确实遇到过这样的问题。
教训是:如果你在使用Oracle/Sun JDK 6,应用里有任何地方用了direct memory,那么使用-XX:+DisableExplicitGC要小心。如果用了该参数而且遇到direct memory的OOM,可以尝试去掉该参数看是否能避开这种OOM。
======================================================================
2、-XX:+DisableExplicitGC 与 Remote Method Invocation (RMI)
看了上一个例子有没有觉得-XX:+DisableExplicitGC参数用起来很危险?那干脆完全不要用这个参数吧。又有什么坑呢?
前段时间有个应用的开发来抱怨,说某次升级JDK之前那应用的GC状况都很好,很长时间都不会发生full GC,但升级后发现每一小时左右就会发生一次。经过对比发现,升级的同时也吧启动参数改了,把原本有的-XX:+DisableExplicitGC给去掉了。
观察到的日志有这么一个特征:
(后面回头再补…先睡觉去了)
前辈们提供的“标准参数”或许适用于他们的应用场景,他们或许也知道这些参数里隐含的陷阱;但听众却不一定知道各种参数背后的缘由。
原则上说,在生产环境使用非标准参数(这里指的是在各JDK/JRE实现特有的、相互之间不通用的参数)应该尽量避免。这些参数与具体实现密切相关,不是光了解很抽象的“JVM原理”就足以理解的;即便在同一系列的JDK/JRE实现中,非标准参数也不保证在各版本间有一样的作用;而且许多人只看名字就猜想参数的左右,做“调优”却适得其反。
非标准参数的默认值在不同版本间或许会悄然发生变化。这些变化的背后多半有合理的理由。设了一大堆非标准参数、不明就里的同学在升级JDK/JRE的时候也容易掉坑里。
下面用Oracle/Sun JDK 6来举几个例子。
======================================================================
1、-XX:+DisableExplicitGC 与 NIO的direct memory
很多人都见过JVM调优建议里使用这个参数,对吧?但是为什么要用它,什么时候应该用而什么时候用了会掉坑里呢?
首先要了解的是这个参数的作用。在Oracle/Sun JDK这个具体实现上,System.gc()的默认效果是引发一次stop-the-world的full GC,对整个GC堆做收集。有几个参数可以改变默认行为,之前发过一帖简单描述过,这里就不重复了。关键点是,用了-XX:+DisableExplicitGC参数后,System.gc()的调用就会变成一个空调用,完全不会触发任何GC(但是“函数调用”本身的开销还是存在的哦~)。
为啥要用这个参数呢?最主要的原因是为了防止某些手贱的同学在代码里到处写System.gc()的调用而干扰了程序的正常运行吧。有些应用程序本来可能正常跑一天也不会出一次full GC,但就是因为有人在代码里调用了System.gc()而不得不间歇性被暂停。也有些时候这些调用是在某些库或框架里写的,改不了它们的代码但又不想被这些调用干扰也会用这参数。
OK。看起来这参数应该总是开着嘛。有啥坑呢?
其中一种情况是下述三个条件同时满足时会发生的:
1、应用本身在GC堆内的对象行为良好,正常情况下很久都不发生full GC;
2、应用大量使用了NIO的direct memory,经常、反复的申请DirectByteBuffer
3、使用了-XX:+DisableExplicitGC
能观察到的现象是:
Log代码
1.java.lang.OutOfMemoryError: Direct buffer memory
2. at java.nio.Bits.reserveMemory(Bits.java:633)
3. at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
4. at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
5....
做个简单的例子来演示这现象:
Java代码
1.import java.nio.*;
2.
3.public class DisableExplicitGCDemo {
4. public static void main(String[] args) {
5. for (int i = 0; i < 100000; i++) {
6. ByteBuffer.allocateDirect(128);
7. }
8. System.out.println("Done");
9. }
10.}
然后编译、运行之:
Command prompt代码
1.$ java -version
2.java version "1.6.0_25"
3.Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
4.Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)
5.$ javac DisableExplicitGCDemo.java
6.$ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC -XX:+DisableExplicitGC DisableExplicitGCDemo
7.Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
8. at java.nio.Bits.reserveMemory(Bits.java:633)
9. at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
10. at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
11. at DisableExplicitGCDemo.main(DisableExplicitGCDemo.java:6)
12.$ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC DisableExplicitGCDemo
13.[GC 10996K->10480K(120704K), 0.0433980 secs]
14.[Full GC 10480K->10415K(120704K), 0.0359420 secs]
15.Done
可以看到,同样的程序,不带-XX:+DisableExplicitGC时能正常完成运行,而带上这个参数后却出现了OOM。
例子里用-XX:MaxDirectMemorySize=10m限制了DirectByteBuffer能分配的空间的限额,以便问题更容易展现出来。不用这个参数就得多跑一会儿了。
在这个例子里,main()里的循环不断申请DirectByteBuffer但并没有引用、使用它们,所以这些DirectByteBuffer应该刚创建出来就已经满足被GC的条件,等下次GC运行的时候就应该可以被回收。
实际上却没这么简单。DirectByteBuffer是种典型的“冰山”对象,也就是说它的Java对象虽然很小很无辜,但它背后却会关联着一定量的native memory资源,而这些资源并不在GC的控制之下,需要自己注意控制好。对JVM如何使用native memory不熟悉的同学可以参考去年JavaOne上IBM的一个演讲,“Where Does All the Native Memory Go”。
Oracle/Sun JDK的实现里,DirectByteBuffer有几处值得注意的地方。
1、DirectByteBuffer没有finalizer,它的native memory的清理工作是通过sun.misc.Cleaner自动完成的。
2、sun.misc.Cleaner是一种基于PhantomReference的清理工具,比普通的finalizer轻量些。对PhantomReference不熟悉的同学请参考Bob Lee最近几年在JavaOne上做的演讲,"The Ghost in the Virtual Machine: A Reference to References"。今年的JavaOne上他也讲了同一个主题,内容比前几年的稍微更新了些。PPT可以从链接里的页面下载到。
Java代码
1./**
2. * General-purpose phantom-reference-based cleaners.
3. *
4. * <p> Cleaners are a lightweight and more robust alternative to finalization.
5. * They are lightweight because they are not created by the VM and thus do not
6. * require a JNI upcall to be created, and because their cleanup code is
7. * invoked directly by the reference-handler thread rather than by the
8. * finalizer thread. They are more robust because they use phantom references,
9. * the weakest type of reference object, thereby avoiding the nasty ordering
10. * problems inherent to finalization.
11. *
12. * <p> A cleaner tracks a referent object and encapsulates a thunk of arbitrary
13. * cleanup code. Some time after the GC detects that a cleaner's referent has
14. * become phantom-reachable, the reference-handler thread will run the cleaner.
15. * Cleaners may also be invoked directly; they are thread safe and ensure that
16. * they run their thunks at most once.
17. *
18. * <p> Cleaners are not a replacement for finalization. They should be used
19. * only when the cleanup code is extremely simple and straightforward.
20. * Nontrivial cleaners are inadvisable since they risk blocking the
21. * reference-handler thread and delaying further cleanup and finalization.
22. *
23. *
24. * @author Mark Reinhold
25. * @version %I%, %E%
26. */
重点是这两句:"A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner."
Oracle/Sun JDK 6中的HotSpot VM只会在年老代GC(full GC/major GC或者concurrent GC都算)的时候才会做reference processing,而在young GC/minor GC时不做。
也就是说,做full GC的话会做reference processing,进而能触发Cleaner对已死的DirectByteBuffer对象做清理工作。而如果很长一段时间里没做过GC或者只做了young GC的话则不会触发Cleaner的工作,那么就可能让本来已经死了的DirectByteBuffer关联的native memory得不到及时释放。
3、为DirectByteBuffer分配空间过程中会显式调用System.gc(),以期通过full GC来强迫已经无用的DirectByteBuffer对象释放掉它们关联的native memory:
Java代码
1.// These methods should be called whenever direct memory is allocated or
2.// freed. They allow the user to control the amount of direct memory
3.// which a process may access. All sizes are specified in bytes.
4.static void reserveMemory(long size) {
5.
6. synchronized (Bits.class) {
7. if (!memoryLimitSet && VM.isBooted()) {
8. maxMemory = VM.maxDirectMemory();
9. memoryLimitSet = true;
10. }
11. if (size <= maxMemory - reservedMemory) {
12. reservedMemory += size;
13. return;
14. }
15. }
16.
17. System.gc();
18. try {
19. Thread.sleep(100);
20. } catch (InterruptedException x) {
21. // Restore interrupt status
22. Thread.currentThread().interrupt();
23. }
24. synchronized (Bits.class) {
25. if (reservedMemory + size > maxMemory)
26. throw new OutOfMemoryError("Direct buffer memory");
27. reservedMemory += size;
28. }
29.
30.}
这几个实现特征使得Oracle/Sun JDK 6依赖于System.gc()触发GC来保证DirectByteMemory的清理工作能及时完成。如果打开了-XX:+DisableExplicitGC,清理工作就可能得不到及时完成,于是就有机会见到direct memory的OOM,也就是上面的例子演示的情况。我们这边在实际生产环境中确实遇到过这样的问题。
教训是:如果你在使用Oracle/Sun JDK 6,应用里有任何地方用了direct memory,那么使用-XX:+DisableExplicitGC要小心。如果用了该参数而且遇到direct memory的OOM,可以尝试去掉该参数看是否能避开这种OOM。
======================================================================
2、-XX:+DisableExplicitGC 与 Remote Method Invocation (RMI)
看了上一个例子有没有觉得-XX:+DisableExplicitGC参数用起来很危险?那干脆完全不要用这个参数吧。又有什么坑呢?
前段时间有个应用的开发来抱怨,说某次升级JDK之前那应用的GC状况都很好,很长时间都不会发生full GC,但升级后发现每一小时左右就会发生一次。经过对比发现,升级的同时也吧启动参数改了,把原本有的-XX:+DisableExplicitGC给去掉了。
观察到的日志有这么一个特征:
(后面回头再补…先睡觉去了)