`
spark998
  • 浏览: 40427 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JVM调优的陷阱

 
阅读更多

开这帖的目的是想让大家了解到,所谓“标准参数”是件很微妙的事情。确实有许多前辈经过多年开发积累下了许多有用的调优经验,但向他们问“标准参数”并照单全收是件危险的事情。

前辈们提供的“标准参数”或许适用于他们的应用场景,他们或许也知道这些参数里隐含的陷阱;但听众却不一定知道各种参数背后的缘由。

原则上说,在生产环境使用非标准参数(这里指的是在各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给去掉了。

观察到的日志有这么一个特征:

(后面回头再补…先睡觉去了)
分享到:
评论

相关推荐

    关键业务系统JVM参数推荐

    3. **JVM调优“标准参数”的陷阱**:R大的文章详细介绍了在不同JDK版本下JVM调优过程中可能遇到的一些陷阱。尽管该文章最初是在JDK 6时撰写的,但是其中提到的很多原则仍然适用,并且随着JDK版本的更新,这些原则也...

    JVM Diagnostics Guide 1.4.2

    5. **性能调优**:涵盖JVM性能监控工具的使用,如JConsole、VisualVM等,以及如何通过调整JVM参数(如-Xms, -Xmx, -XX:NewRatio等)来优化内存分配和垃圾收集策略。 6. **线程分析**:讲解如何理解和诊断线程问题,...

    java陷阱常见面试题

    10. 描述如何优化Java服务器的性能,包括JVM调优和数据库优化。 通过深入理解和实践这些Java陷阱,不仅可以避免在编程中犯错,也能在面试中展现出专业技能,为你的职业生涯加分。不断学习和探索,使你在Java的世界...

    Java语言规范和JVM规范官网文档

    通过学习这些规范,开发者可以更准确地理解代码的运行机制,避免常见的编程陷阱,写出更高效、更易于维护的代码。 在Java7中,引入了try-with-resources语句,使得资源管理更加简洁和安全。Java8引入了lambda表达式...

    Java程序员面试陷阱共48页.pdf.zip

    7. **JVM调优**:理解JVM内存模型,如堆内存、栈内存、方法区等,以及如何进行性能优化,包括堆大小设置、类加载机制等。 8. **IO/NIO/BIO**:理解三种I/O模型的特点,尤其是NIO(非阻塞I/O)在高性能服务器编程中...

    Java程序员面试可能遭遇的个专业技术陷阱解析.pdf,这是一份不错的文件

    4. **JVM调优** - 面试陷阱:面试官可能会要求解释JVM的内存模型,或者如何设置JVM参数进行性能优化。 - 解析:Java虚拟机有堆内存(包括新生代、老年代)、方法区、栈等区域。理解各区域的作用,如如何设置-Xms、...

    Java.Bug模式详解pdf

    8. **JVM调优**:理解JVM的工作原理和调优策略对优化Java应用至关重要。书中可能涵盖堆内存设置、垃圾收集器选择、类加载机制等方面的常见问题。 9. **多线程同步机制**:synchronized、volatile、java.util....

    java面试陷阱题

    4. **JVM优化**:熟悉JVM参数调优,如-Xms、-Xmx、-XX:NewRatio等。理解类加载器的双亲委派模型。 5. **集合框架**:深入理解ArrayList、LinkedList、HashSet、HashMap等集合类的内部实现,包括它们的时间复杂度和...

    Java问题定位技术.pdf

    总结来说,这份文档是一份相当全面的Java问题定位与性能分析指南,覆盖了从基础的问题诊断方法到高级的性能调优策略,以及内存管理、并发编程和JVM调优等核心知识。对于任何从事Java开发和性能调优的工程师来说,这...

    Java问题定位技术(

    Java问题定位技术涉及到多方面知识点,从JVM到多线程、高并发以及性能调优工具等都是深入理解Java性能问题的核心组成部分。下面详细介绍这些知识点。 首先,JVM(Java虚拟机)是运行Java字节码的虚拟机进程,它是...

    java程序员面试陷阱

    5. **JVM优化**:了解JVM的运行机制,包括类加载、内存模型、垃圾收集器和性能调优,这些都是面试中常见的问题。例如,理解堆内存的分代模型,知道如何分析和调整JVM参数以优化应用性能。 6. **集合框架**:深入...

    Java虚拟机工作原理

    ### Java虚拟机(JVM)工作原理深度解析 Java虚拟机(JVM)是Java技术的核心组件之...通过对JVM工作原理的深入了解,开发人员能够更有效地编写和优化Java应用程序,利用JVM提供的强大功能,同时避免常见的陷阱和性能瓶颈。

    云计算系统架构文档 汇总上

    Twitter的JVM性能调优经验分享(Attila Szegedi) pdf Web请求异步处理和海量数据即时分析在淘宝开放平台的实践 岑文初 pdf 把大象放进冰箱 技术型复杂项目的特性裂解 pdf 阿里巴巴 B2B 的服务框架探索 钱霄 pdf...

    Java_Performance.zip

    这本书详尽地阐述了如何理解和改善Java应用程序的性能,包括JVM(Java虚拟机)的工作原理、性能分析工具的使用以及实际调优策略。 在Java性能优化中,了解JVM的工作机制至关重要。JVM是Java程序运行的基础,它负责...

    java 开发手册 泰山版.zip

    9. **JVM优化**:分析垃圾回收机制、内存模型、类加载机制,提供JVM调优策略。 10. **设计模式**:介绍常见的设计模式,如工厂模式、单例模式、观察者模式等,帮助开发者解决常见问题。 11. **Spring框架**:涵盖...

    java编程事项(转载收集整理版)

    11. **Java虚拟机(JVM)**:理解JVM的工作原理,包括类加载机制、内存模型(堆、栈、方法区等)以及JVM调优,有助于提升程序性能。 12. **Java 8及以后的特性**:从Java 8开始,引入了Lambda表达式、Stream API和...

    java设计模式与经验总结

    这部分内容可能涵盖了Java开发中的各种实践经验和技巧,比如性能优化、异常处理、内存管理、多线程同步、IO流操作、JVM调优等方面。在实际开发中,理解这些经验能够帮助开发者避免常见的陷阱,提升代码质量。例如,...

    Java面试资料精讲分析.txt

    - **JVM调优**:JVM内存模型、GC算法、性能监控工具。 4. **设计模式** - **工厂模式**:单例模式、工厂方法模式、抽象工厂模式。 - **结构模式**:适配器模式、代理模式、装饰者模式等。 - **行为模式**:观察...

    Java_Performance_Tuning_2nd

    如今,通过合理避免常见性能陷阱,任何Java程序都能达到令人满意的运行速度。本书提供了详尽的指导,帮助开发者进行性能调优。 #### 三、调优流程详解 ##### 3.1 设定目标 在开始性能调优之前,明确调优的目标至关...

Global site tag (gtag.js) - Google Analytics