目录
梳理了class文件的结构、类的各种加载器、类的加载过程、虚拟机内存模型、虚拟机垃圾收集器以及各种垃圾收集算法,最终目的就是为了对Java虚拟机进行性能调优。性能调优包含多个层次,架构调优、代码调优、jvm调优、数据库调优、操作系统调优。性能调优首先明确优化的目标、分析性能瓶颈、提出优化方案、性能优化,然后通过监控和数据统计工具确认是否达到目标。
何时进行JVM调优
Heap内存持续上涨达到设置的最大值、Full GC频繁、GC停顿时间过长超过1秒、应用出现OutOfMemory等内存异常、应用中使用本地缓存且占用大量内存空间、系统吞吐量和响应性能不高或下降。
JVM调优基本原则
JVM调优是一个手段,但并不一定所有问题都可以通过JVM进行调优解决,所以进行JVM调优时要遵循一些原则,大多数的Java应用不需要进行JVM优化、大多数导致GC问题的原因是代码层面的问题导致的(代码层面)、上线前应先考虑将机器的JVM参数设置到最优、减少创建对象的数量(代码层面)、减少使用全局变量和大对象(代码层面)、优先架构调优和代码调优,JVM优化是不得已的手段(代码、架构层面)、分析GC情况优化代码比优化JVM参数更好(代码层面)。
JVM调优的目标
GC低停顿和GC低频率、低内存占用、高吞吐量。量化目标:Heap内存使用率 <=70%,Old generation内存使用率<=70%,avgpause \u003C= 1秒,Full gc次数0或avg pause interval >= 24小时。
JVM调优步骤
分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点。确定JVM调优量化目标。确定JVM调优参数(根据历史JVM参数来调整)。依次调优内存、延迟、吞吐量等指标。对比观察调优前后的差异。不断的分析和调整,直到找到合适的JVM参数配置。找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。
JVM参数调优
比如以下参数示例:
-Xmx4g –Xms4g –Xmn1200m –Xss512k -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:PermSize=100m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15\
上面为Java7及以前版本的示例,在Java8中永久代的参数-XX:PermSize和-XX:MaxPermSize已经失效。
-Xmx4g:堆内存最大值为4GB。
-Xms4g:初始化堆内存大小为4GB。
-Xmn1200m:设置年轻代大小为1200MB。增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss512k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1MB,以前每个线程堆栈大小为256K。应根据应用线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=8:设置年轻代中Eden区与Survivor区的大小比值。设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-XX:PermSize=100m:初始化永久代大小为100MB。
-XX:MaxPermSize=256m:设置持久代大小为256MB。
-XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。新生代、老生代、永久代的参数,如果不进行指定,虚拟机会自动选择合适的值,同时也会基于系统的开销自动调整。可调优参数:-Xms:初始化堆内存大小,默认为物理内存的1/64(小于1GB)。
-Xmx:堆内存最大值。默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
-Xmn:新生代大小,包括Eden区与2个Survivor区。
-XX:SurvivorRatio=1:Eden区与一个Survivor区比值为1:1。
-XX:MaxDirectMemorySize=1G:直接内存。报java.lang.OutOfMemoryError: Direct buffer memory异常可以上调这个值。
-XX: DisableExplicitGC:禁止运行期显式地调用System.gc()来触发fulll GC。注意: Java RMI的定时GC触发机制可通过配置-Dsun.rmi.dgc.server.gcInterval=86400来控制触发的时间。
-XX:CMSInitiatingOccupancyFraction=60:老年代内存回收阈值,默认值为68。-XX:ConcGCThreads=4:CMS垃圾回收器并行线程线,推荐值为CPU核心数。-XX:ParallelGCThreads=8:新生代并行收集器的线程数。
-XX:MaxTenuringThreshold=10:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
-XX:CMSFullGCsBeforeCompaction=4:指定进行多少次fullGC之后,进行tenured区 内存空间压缩。
-XX:CMSMaxAbortablePrecleanTime=500:当abortable-preclean预清理阶段执行达到这个时间时就会结束。在设置的时候,如果关注性能开销的话,应尽量把永久代的初始值与最大值设置为同一值,因为永久代的大小调整需要进行FullGC才能实现。
调优案例
一个小系统,使用32位JDK,4G内存,测试期间发现服务端不定时抛出内存溢出异常。 加入 -XX:+HeapDumpOnOutOfMemoryError(添加这个参数后,堆内存溢出时就会输出异常日志), 但再次发生内存溢出时,没有生成相关异常日志。
分析,在32位JDK上,1.6G分配给堆,还有一部分分配给JVM的其他内存,直接内存最大也只能在剩余的0.4G空间中分出一部分, 如果使用了NIO,JVM会在JVM内存之外分配内存空间,那么就要小心“直接内存”不足时发生内存溢出异常了。
直接内存的回收过程
直接内存虽然不是JVM内存空间,但它的垃圾回收也由JVM负责。垃圾收集进行时,虚拟机虽然会对直接内存进行回收, 但是直接内存却不能像新生代、老年代那样,发现空间不足了就通知收集器进行垃圾回收, 它只能等老年代满了后 Full GC,然后“顺便”帮它清理掉内存的废弃对象。 否则只能一直等到抛出内存溢出异常时,先 catch 掉,再在 catch 块里大喊 “System.gc()
”。 要是虚拟机还是不听,那就只能眼睁睁看着堆中还有许多空闲内存,自己却不得不抛出内存溢出异常了。