《深入理解JVM内幕之JVM简单调优参数》要点:
本文介绍了深入理解JVM内幕之JVM简单调优参数,希望对您有用。如果有疑问,可以联系我们。
理想的情况下,一个Java程序使用JVM的默认设置也可以运行得很好,所以一般来说,没有必要设置任何JVM参数.然而,由于一些性能问题(很不幸的是,这些问题经常出现),我们必须面对相关的JVM调优参数.
不管是YGC还是Full GC,GC过程中都会对导致程序运行中中断,正确的选择不同的GC策略,调整JVM、GC的参数,可以极大的减少由于GC工作,进而适当的提高Java程序的工作效率.但是调整GC是以个极为复杂的过程,由于各个程序具备不同的特点,并且跑在各个机器上的配置不同,所以使用的GC种类也会不同.
所有已制定的HotSpot内存管理和垃圾回收算法都基于一个相同的堆内存划分:新生代(young generation)里存储着新分配的和较年轻的对象,老年代(old generation)里存储着长寿的对象.在此之外,永久代(permanent generation)存储着那些需要伴随整个JVM生命周期的对象.
下面我们就来看一下几个简单常用的JVM调优参数(-Xms、-Xmx、-XX:+HeapDumpOnOutOfMemoryError、-XX:HeapDumpPath、-XX:OnOutOfMemoryError、 -XX:PermSize、-XX:MaxPermSize……):
-Xms and -Xmx (or: -XX:InitialHeapSize and -XX:MaxHeapSize)
-Xms和-Xmx可以说是最流行的JVM参数,它们可以允许我们指定JVM的初始和最大堆内存大小.一般来说,这两个参数的数值单位是Byte,但同时它们也支持使用速记符号,比如“k”或者“K”代表“kilo”,“m”或者“M”代表“mega”,“g”或者“G”代表“giga”.举个例子,下面的命令启动了一个初始化堆内存为128M,最大堆内存为2G,名叫“MyApp”的Java应用程序.
$ java -Xms128m -Xmx2g MyApp
在实际使用过程中,初始化堆内存的大小通常被视为堆内存大小的下界.然而JVM可以在运行时动态的调整堆内存的大小,所以理论上来说我们有可能会看到堆内存的大小小于初始化堆内存的大小.但是即使在非常低的堆内存使用下,我也从来没有遇到过这种情况.这种行为将会方便开发者和系统管理员,因为我们可以通过将“-Xms”和“-Xmx”设置为相同大小来获得一个固定大小的堆内存. -Xms和-Xmx实际上是-XX:InitialHeapSize和-XX:MaxHeapSize的缩写.我们也可以直接使用这两个参数,它们所起得效果是一样的:
$ java -XX:InitialHeapSize=128m -XX:MaxHeapSize=2g MyApp
需要注意的是,所有JVM关于初始/最大堆内存大小的输出都是使用它们的完整名称:“InitialHeapSize”和“MaxHeapSize”.所以当你查询一个正在运行的JVM的堆内存大小时,如使用-XX:+PrintCommandLineFlags参数或者通过JMX查询,你应该寻找“InitialHeapSize”和“MaxHeapSize”标志而不是“Xms”和“Xmx”.
-XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath
如果我们没法为-Xmx(最大堆内存)设置一个合适的大小,那么就有可能面临内存溢出(OutOfMemoryError)的风险,这可能是我们使用JVM时面临的最可怕的猛兽之一.导致内存溢出的根本原因需要仔细的定位.通常来说,分析堆内存快照(Heap Dump)是一个很好的定位手段,如果发生内存溢出时没有生成内存快照那就实在是太糟了,特别是对于那种JVM已经崩溃或者错误只出现在顺利运行了数小时甚至数天的生产系统上的情况.
幸运的是,我们可以通过设置-XX:+HeapDumpOnOutOfMemoryError 让JVM在发生内存溢出时自动的生成堆内存快照.有了这个参数,当我们不得不面对内存溢出异常的时候会节约大量的时间.默认情况下,堆内存快照会保存在JVM的启动目录下名为java_pid<pid>.hprof 的文件里(在这里<pid>就是JVM进程的进程号).也可以通过设置-XX:HeapDumpPath=<path>来改变默认的堆内存快照生成路径,<path>可以是相对或者绝对路径.
虽然这一切听起来很不错,但有一点我们需要牢记.堆内存快照文件有可能很庞大,特别是当内存溢出错误发生的时候.因此,我推荐将堆内存快照生成路径指定到一个拥有足够磁盘空间的地方.
-XX:OnOutOfMemoryError
当内存溢出发生时,我们可以执行一些指令,比如发个E-mail通知管理员或者执行一些清理工作.通过-XX:OnOutOfMemoryError 这个参数我们可以做到这一点,这个参数可以接受一串指令和它们的参数.比如在下面的例子中,当内存溢出错误发生的时候,我们可以将堆内存快照写到/tmp/heapdump.hprof 文件并且在JVM的运行目录执行脚本cleanup.sh
$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError ="sh ~/cleanup.sh" MyApp
-XX:PermSize and -XX:MaxPermSize
永久代在堆内存中是一块独立的区域,它包含了所有JVM加载的类的对象表示.为了成功运行应用程序,JVM会加载很多类(因为它们依赖于大量的第三方库,而这又依赖于更多的库并且需要从里面将类加载进来)这就需要增加永久代的大小.我们可以使用-XX:PermSize 和-XX:MaxPermSize 来达到这个目的.其中-XX:MaxPermSize 用于设置永久代大小的最大值,-XX:PermSize 用于设置永久代初始大小.下面是一个简单的例子:
$ java -XX:PermSize=128m -XX:MaxPermSize=256m MyApp
请注意,这里设置的永久代大小并不会被包括在使用参数-XX:MaxHeapSize 设置的堆内存大小中.也就是说,通过-XX:MaxPermSize设置的永久代内存可能会需要由参数-XX:MaxHeapSize 设置的堆内存以外的更多的一些堆内存
-XX:InitialCodeCacheSize and -XX:ReservedCodeCacheSize
JVM一个有趣的,但往往被忽视的内存区域是“代码缓存”,它是用来存储已编译方法生成的本地代码.代码缓存确实很少引起性能问题,但是一旦发生其影响可能是毁灭性的.如果代码缓存被占满,JVM会打印出一条警告消息,并切换到interpreted-only 模式:JIT编译器被停用,字节码将不再会被编译成机器码.因此,应用程序将继续运行,但运行速度会降低一个数量级,直到有人注意到这个问题.就像其他内存区域一样,我们可以自定义代码缓存的大小.相关的参数是-XX:InitialCodeCacheSize 和-XX:ReservedCodeCacheSize,它们的参数和上面介绍的参数一样,都是字节值.
-XX:+UseCodeCacheFlushing
如果代码缓存不断增长,例如,因为热部署引起的内存泄漏,那么提高代码的缓存大小只会延缓其发生溢出.为了避免这种情况的发生,我们可以尝试一个有趣的新参数:当代码缓存被填满时让JVM放弃一些编译代码.通过使用-XX:+UseCodeCacheFlushing 这个参数,我们至少可以避免当代码缓存被填满的时候JVM切换到interpreted-only 模式.不过,我仍建议尽快解决代码缓存问题发生的根本原因,如找出内存泄漏并修复它.
关于调优我们一般有如下几条经验、规则:
新生代大小选择:响应时间优先的应用尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,新生代收集发生的频率也是最小的,同时减少到达老年代的对象;吞吐量优先的应用尽可能的设置大,可能到达Gbit的程度,因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用;避免设置过小,当新生代设置过小时会导致:①YGC次数更加频繁 ②可能导致YGC对象直接进入老年代,如果此时老年代满了,会触发FGC.
老年代大小选择:响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数,如果堆设置小了,可能会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式,如果堆大了,则需要较长的收集时间,最优化的方案一般需要参考以下数据获得:并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在新生代和老年代回收上的时间比例;吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代,因为这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽量存放长期存活对象.
较小堆引起的碎片问题:因为老年代的并发收集器使用标记清除算法所以不会对堆进行压缩,当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象,但是当堆空间较小时,运行一段时间以后就会出现"碎片",如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记清除方式进行回收,如果出现"碎片"可能需要进行如下配置:-XX:+UseCMSCompactAtFullCollection 使用并发收集器时,开启对老年代的压缩 -XX:CMSFullGCsBeforeCompaction=0 上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩.
用64位操作系统,Linux下64位的jdk比32位jdk要慢一些,但是吃得内存更多,吞吐量更大
Xmx和Xms设置一样大,MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力
使用CMS的好处是用尽量少的新生代,经验值是128M-256M, 然后老年代利用CMS并行收集, 这样能保证系统低延迟的吞吐效率. 实际上CMS的收集停顿时间非常的短,2G的内存, 大约20-80ms的应用程序停顿时间
系统停顿的时候可能是GC的问题也可能是程序的问题,多用jmap和jstack查看,或者killall -3 java,然后查看java控制台日志,能看出很多问题.
仔细了解自己的应用,如果用了缓存,那么老年代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定.
采用并发回收时,新生代要小一点,老年代要大一点,因为老年代用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿
JVM参数的设置(特别是 –Xmx –Xms –Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold等参数的设置没有一个固定的公式,需要根据PV old区实际数据 YGC次数等多方面来衡量.为了避免promotion faild可能会导致Xmn设置偏小,也意味着YGC的次数会增多,处理并发访问的能力下降等问题.每个参数的调整都需要经过详细的性能测试,才能找到特定应用的最佳配置.
附:
JVM参数的含义
并行收集器相关参数
CMS相关参数
辅助信息
--END
《深入理解JVM内幕之JVM简单调优参数》是否对您有启发,欢迎查看更多与《深入理解JVM内幕之JVM简单调优参数》相关教程,学精学透。维易PHP学院为您提供精彩教程。
转载请注明本页网址:
http://www.vephp.com/jiaocheng/8000.html