转自:http://macrochen.iteye.com/blog/599698
http://pengjiaheng.spaces.live.com/blog/cns!2DAA368B386E6AEA!770.entry 这个讲的比较全面, 比较入门
http://blog.csdn.net/Peart_Boy/archive/2006/10/01/1313413.aspx 讲的基本概念不错, 虽然比较老(jdk1.3)
http://www.blogjava.net/yoda/archive/2008/04/14/192633.html 这个是jdk1.4的
http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html 这个是最新的(jdk1.5), 可以与上面的中文的对照来看
http://blogs.sun.com/watt/resource/jvm-options-list.html 很全的参数说明(各个版本, 各个平台)
http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm jvm参数大全(jdk6 中文版)
http://blog.csdn.net/calvinxiu/archive/2007/05/18/1614473.aspx 这个对并行, 并发收集讲的比较好
http://www.iteye.com/topic/212967 比较有借鉴的调优实战
http://www.meichua.com/archives/tag/java-tool 常用jvm辅助工具
基本概念
stack and heap
stack, 又称线程栈, 一个线程独占, heap则会被多个线程共享, stack解决了线程如何执行, 如何处理数据, heap解决了数据如何存放, 存放在哪儿的问题. heap中存的是对象。stack中存的是基本数据类型和堆中对象的引用。Heap是 Java 程序的对象生活的地方,包含活的对象,死的对象以及剩余内存。
当对象不能被运行中的程序的指针所到达时,这些对象成为”垃圾“。
JVM 的堆大小决定了 VM 花费在收集垃圾上的时间和频度。
使用这个方法可以得到应用的空间使用量
System.out.println(Runtime.getRuntime().totalMemory());
heap结构
堆划分为两个区域:新生代和旧生代。新生代(Young)又分为:Eden 和两片生存空间(survivor spaces)。其中保证有一片空间在任何时间是空的,当垃圾收集发生时, Eden 中的活的对象复制到下一片生存空间,对象就在生存空间之间复制,直到到达最大门限值(老化),然后复制到旧生代。Eden 是新的对象分配的地方。
当-Xms参数的值比-Xmx小的时候, 会存在一个虚拟空间.
如果堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。
线程运行时需要分配内存, gc则负责回收内存, 为了解决二者的矛盾, 必须在gc的时候停止线程的运行来达到阻止分配内存的目的.
为了优化GC,最好让-Xmn值约等于-Xmx的1/3
Young代的默认值为4M,随堆内存增大,约为1/15, -XX:NewRatio= 参数可以设置Young与Old的大小比例,-server时默认为1:2,但实际上young启动时远低于这个比率?如果信不过JVM,也可以用 -Xmn硬性规定其大小,有文档推荐设为Heap总大小的1/4。
Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。
垃圾收集(GC)
垃圾收集可以接受的速度与应用有关,应该通过分析实际的垃圾收集的时间和频率来调整。
如果堆的大小很大,那么完全垃圾收集就会很慢,但是频度会降低。
调整堆大小的的目的是最小化垃圾收集的时间,以在特定的时间内最大化处理客户的请求。
对象在年轻代生存的时间越长,需要的收集时间也越长,因此,收集变慢。
你的应用建立和释放对象的速度决定了垃圾收集的频度。因此,在编程时,应注意使用对象的缓冲,而不是新建立对象。
大多数对象在新生代就已经死去,因此你能有效的调整垃圾收集。如果你能安排大多数对象的生存期小于一个收集时间,那么,垃圾收集是十分高效的。
暂停时间的含义是应用因为垃圾收集而显示出来的短暂停顿。
吞吐量的含义是在一段比较长的时间内,没有用在垃圾收集的时间和总时间的百分比。
减少暂停时间的办法可以采用小的年轻代和增量收集,但是这以牺牲吞吐率为代价。
很大的新生代能提高吞吐率,但是会带来暂停时间的增加。
当某个代被充满的时候,就会发生垃圾收集,所以,吞吐量和可用内存的大小成反比,总内存大小是影响垃圾收集的最重要因素。
年轻代大点就不用频繁GC,而且增大GC的间隔后,可以让多点对象自己死掉而不用复制了。但Young增大时,GC造成的停顿时间会相应增加
并行(Parallel)与并发(Concurrent)仅一字之差,并行指多条垃圾收集线程并行,并发指用户线程与垃圾收集线程并发,程序在继续运行,而垃圾收集程序运行于另一个个CPU上。并行指多条垃圾收集线程并行,并发指用户线程与垃圾收集线程并发.
full gc对整个堆进行整理,包括Young、Tenured和Perm。
年轻代的复制收集,依然必须停止所有应用程序线程,原理如此,只能靠多CPU,多收集线程并发来提高收集速度
常用JVM参数说明
以 -X 开头的选项都为非标准选项(并不能在所有的 VM 上实现),在后续的版本中可能会不通知而变更。
由于 -XX 选项需要特别的系统权限,因此不建议随便使用。
-verbosegc
使用 -verbosegc 选项测量有多少时间和资源用于垃圾收集。
通过使用虚拟机选项-verbose:gc,能够将每次收集时的一些信息打印出来
上面的输出中,表明虚拟机进行了两次次要收集和一次主要收集。箭头两端的数字
325407K->83000K(第一行)
表示在进行垃圾收集之前和之后活动对象的总大小。在次要收集之后的数字(83000K)包含了那些并不是必要活动的对象,但是还不能被回收。因为 这些对象可能本身是活动的,或者有来自旧生代的引用。括弧中的数字(776768K)(第一行)表示总共的可用空间的大小。这个大小不包括持久代在内,是 堆的大小减去一个存活空间的大小。此次次要收集大约用了0.2300771 secs (第一行)
如果使用了-XX:+PrintGCDetail选项,就会将垃圾收集时的详细信息打印出来
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]]
从上面的输出信息可以看出,次要收集释放了新生代大约98%的空间(DefNew: 64575K->959K(64576K))
用了大约46毫秒(0.0457646 secs)
整个堆空间的使用率下降到51%左右(196016K->133633K(261184K))
最后看到,相对于新生代的收集,在时间消耗上稍微多了一点点(0.0459067 secs)
-XX:+DisableExplicitGC 关闭程序中的System.gc()调用
-XX:+PrintGCApplicationConcurrentTime 打印每次垃圾回收前,程序未中断的执行时间。
输出形式:Application time: 0.5291524 seconds
-XX:+PrintGCApplicationStoppedTime打印垃圾回收期间程序暂停的时间。
输出形式:Total time for which application threads were stopped: 0.0468229 seconds
-Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。
-XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
输出形式:
-XX:+AggressiveHeap 特别说明下:(我感觉对于做java cache应用有帮助)
* 试图是使用大量的物理内存
* 长时间大内存使用的优化,能检查计算资源(内存, 处理器数量)
* 至少需要256MB内存
* 大量的CPU/内存, (在1.4.1在4CPU的机器上已经显示有提升)
-XX:+UseParNewGC 允许多线程收集新生代
-XX:+CMSParallelRemarkEnabled 降低标记停顿
-XX+UseCMSCompactAtFullCollection 在FULL GC的时候, 压缩内存, CMS是不会移动内存的, 因此, 这个非常容易产生碎片, 导致内存不够用, 因此, 内存的压缩这个时候就会被启用。 增加这个参数是个好习惯。
-XX:+UseCMSInitiatingOccupancyOnly 仅仅使用手动定义初始化定义开始CMS收集
-XX:CMSInitiatingOccupancyFraction=70 年老代上, 使用70%后开始CMS收集
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入 年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻 代的存活时间,增加在年轻代即被回收的概率。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
-XX:+GCTimeRatio=19 垃圾回收时间(1)与非垃圾回收时间(19)的比值, 表示java可以用5%的时间来做垃圾回收,1/(1+19)=1 /20=5%, 默认情况为99,即1%的时间用于垃圾回收。
-XX:+CMSParallelRemarkEnabled 减少cms中第二次remark的时间
-XX:+CMSScavengeBeforeRemark选项,强制remark之前开始一次minor gc, 减少remark的暂停时间,但是在remark之后也将立即开始又一次minor gc。
GC信息的格式
收集器
-XX:+UseParallelGC 。吞吐收集器(已默认无需配置的参数: -XX:+UseAdaptiveSizePolicy(动态调整新生代大小)). 并行收集使用多线程处理垃圾回收工作,因而速度快,效率高。而且理论上CPU数目越多,越能体现出并行收集器的优势。在新生代使用并行收集策略, 当你的应用运行在多个处理器的主机上时,考虑使用吞吐收集器。应用中有很多线程都在创建对象时, 需要增大新生代的大小。在只有1颗CPU的主机上,吞吐收集器的性能表现可能还不如默认收集器,因为一些并行执行(例如进行同步时的开销)带来了额外的开 销;在2颗CPU的主机上,吞吐收集器和默认收集器性能相当;在多于2颗CPU的主机上,你会发现进行次要收集的暂停时间降低了。 年轻代暂停应用程序,多个垃圾收集线程并行的复制收集,线程数默认为CPU个数,CPU很多时,可用–XX:ParallelGCThreads=减少线 程数。增加收集线程, 会增加旧生代的内存碎片. 参数-XX:+UseAdaptiveSizePolicy(默认打开)。该特征对于收集时间、分配比例、收集之后堆的空闲空间等数据进行统计分析,然后 以此为依据调整新生代和旧生代的大小以达到最佳效果。-XX:+AggressiveHeap选项会检测主机的资源(内存大小、处理器数量),然后调整相 关的参数,使得长时间运行的、内存申请密集的任务能够以最佳状态运行。吞吐收集器(-XX:+UseParallelGC)、大小自适应 (-XX:+UseAdaptiveSizePolicy)以及本选项(-XX:+AggressiveHeap)经常结合在一起使用。对年轻代的复制收 集,依然必须停止所有应用程序线程,因此只能靠多CPU,多收集线程并发来提高收集速度
-XX:+UseConcMarkSweepGC 。并发收集器(已默认无需配置的参 数:-XX:+UseParNewGC(Parallel收集新生代) -XX:+CMSPermGenSweepingEnabled(CMS收集持久代) -XX:UseCMSCompactAtFullCollection(full gc时压缩年老代)). 在旧生代使用并发收集策略,大部分收集工作都是和应用并发进行的, 在进行收集的时候,应用的暂停时间很短(两次短暂停,其他时间应用程序与收集线程并发的清除)。如果设置一个合适的旧生代大小,能够达到非常良好的效果。 并发收集器旨在降低旧生代上进行收集的暂停时间. 每当发生主要收集的时候,并发收集器在收集开始的时候和中期会短时间的暂停所有的应用线程,中期的暂停时间相对长一点,在这次暂停中,多个线程同时工作完 成收集任务。剩余的收集工作将由一个收集线程完成,这次是和应用并发执行的。虽然降低了暂停时间,但是并发收集确实占用了一部分处理器资源,所以你可能感 觉到应用会有所缓慢。N的值越大,在并发收集上所用的处理器资源就越少,并发收集器的优势就越明显。当采用-XX:+PrintGCDetails参数和 -verbose:gc输出,我们可以看到并发收集的输出中穿插了很多次要收集,一个并发收集周期中,会有多次的次要收集。CMS-initial- mark表示并发收集周期的开始,CMS-concurrent-mark表示并发标识阶段的结束,CMS-concurrent-sweep表示并发清 理阶段的结束。在多处理器平台上,可以使用参数-XX:+UseParNewGC来降低次要收集的暂停时间, 如果使用了UseParNewGC,那么同时使用-XX:+CMSParallelRemarkEnabled参数可以降低标识暂停. 并发收集一开始会很短暂的停止一次所有线程来开始初始标记根对象,然后标记线程与应用线程一起并发运行,最后又很短的暂停一次,多线程并行的重新标记之前 可能因为并发而漏掉的对象,然后就开始与应用程序并发的清除过程。可见,最长的两个遍历过程都是与应用程序并发执行的,比以前的串行算法改进太多. 串行标记清除是等年老代满了再开始收集的,而并发收集因为要与应用程序一起运行,如果满了才收集,应用程序就无内存可用,所以系统默认68%满的时候就开 始收集。内存已设得较大,吃内存又没有这么快的时候,可以用-XX:CMSInitiatingOccupancyFraction=恰当增大该比率。 使用CMS的前提条件是你有比较的长生命对象(比如有200M以上的OLD堆占用).
-Xincgc 。增量收集器(jdk1.5已停止维护).
调优原则
两个重要的优化途径:总执行时间的最小化和由垃圾收集所引起的暂停时间的最小化。另一个选项是最小化花在执行垃圾收集操作上的总时间。
如果系统花费很多的时间收集垃圾,请减小堆大小。
一次完全的垃圾收集(full gc)应该不超过 3-5 秒。
一般说来,你应该使用物理内存的 80% 作为堆大小。
除非你遇到暂停的问题,否则,可以分配足够的内存给 JVM,缺省的 64MB 总是太小。
设置-Xms和-Xmx一样大,以避免虚拟机在收缩大小时的消耗。
NewSize 和 MaxNewSize 绑定新生代的长度的低端和高端,设置为一样大小时和 -Xms 和 -Xmx 一样解决新生代的预测时间。最好将新生代的大小设定为经过NewRatio参数计算出的大小的整倍数。
当增加了处理器之后,要相应的增加虚拟机内存大小,因为内存分配是可以并行进行的
对于需要很大堆内存的服务器应用来说,大于堆虚拟约定内存一半的Eden大小是没有任何意义的:只会发生主要收集。
如果有特殊需要,使用参数SurvivorRatio可以调整存活空间的大小,但是通常这个参数对性能的影响不那么重要。例 如,-XX:SurvivorRatio=6就设定了每个存活空间和Eden的比率为1 :6,换句话说,就是每个存活空间占新生代大小的1/8(不是1/7,因为有两个存活空间)。
年轻代大小选择:
* 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
* 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
一般的Young Generation的大小是整个Heap size的1/4。Young generation的minor收集率应一般在70%以上。当然在实际的应用中需要根据具体情况进行调整。
调优技巧
除非你碰到过度的大收集或者暂停时间,否则分配足够的内存给新生代,缺省的 MaxNewSize (32MB) 往往太小。
先使用默认收集器,找到一个新生代和旧生代大小的合适的估算值,然后将旧生代的大小设置成和新生代一样大再去使用并发收集器。这只是一个非常粗略的近似值,至于实际上的最佳设置是由应用来决定的。
当旧生代在填满之前如果收集工作无法全部完成,那么就会暂停应用的线程来完成所有的收集工作。这就是我们所说的完全收集(full collections)。如果发现完全收集比较频繁,可能需要调整并发收集的相关参数。
在使用增量收集器前, 首先使用默认收集器找到一个合适的堆大小,如果此时主要收集的暂停时间还是无法满足应用需求,尝试调整各个代的大小,并且使用增量收集器,直到找到合适的堆设置。
使用CMS的好处是用尽量少的新生代、我的经验值是128M-256M, 然后老生代利用CMS并行收集, 这样能保证系统低延迟的吞吐效率。 实际上cms的收集停顿时间非常的短,2G的内存, 大约20-80ms的应用程序停顿时间。
新生代越大,次要回收发生的次数就越少。当然,对于一定大小的堆来说,新生代越大,就意味着旧生代越小,那么就会使得主要回收的次数增多。最佳选择应该参考应用创建的对象的存活周期来设定。
如果存活空间设置的太小,就会导致过于频繁的向旧生代复制对象;如果存活空间设置的太大,就会因为空闲而白白浪费。虚拟机的垃圾收集器都会为对象 在复制到旧生代之前的反转复制次数选择一个阀值,通过设定这个阀值来保证存活空间一直处于半满状态。通过指定 -XX:+PrintTenuringDistribution参数可以观察到这个阀值,以及新生代中对象的"年龄".
对于Web服务器应用来说,吞吐量是要着重考虑的,而暂停时间可能由于网络的反应时间而不那么明显;而对于一个交互式图形界面的应用来说,即使是短暂的暂停都会带来非常不好的用户体验。
设置代的大小是在这些考虑因素之间作出的一个权衡。将新生代设置得很大会得到很好的吞吐性能,但是会增加暂停时间;反之,较小的新生代设置会减小暂停时间,但是降低了吞吐量。一个代的大小不应该影响在其他代上进行垃圾收集的频率和暂停时间。
采用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿。
参见问题说明
1.常见的出现java.lang.StackOverflowError 异常是无法返回的递归
2.promotion failed 错误的原因是CMS来不及回收(CMS默认在年老代占到90%左右才会 执行),年老代又没有足够的空间供GC把一些活的对象从年轻代移到年老代,所以执行Full GC. 一般可能是两种原因产生,第一个原因是救助空间不够,救助空间里的对象还不应该被移动到年老代,但年轻代又有很多对象需要放入救助空间;第二个原因是年老 代没有足够的空间接纳来自年轻代的对象;这两种情况都会转向Full GC,网站停顿时间较长。第一个原因我的最终解决办法是去掉救助空间,设置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二个原因我的解决办法是设置 CMSInitiatingOccupancyFraction为某个值(假设70),这样年老代空间到70%时就开始执行CMS,年老代有足够的空间接 纳来自年轻代的对象。CMSInitiatingOccupancyFraction,这个参数设置有很大技巧,基本上满足(Xmx-Xmn)* (100- CMSInitiatingOccupancyFraction)/100>=Xmn就不会出现promotion failed。在我的应用中Xmx是6000,Xmn是500,那么Xmx-Xmn是5500M,也就是年老代有5500 M,CMSInitiatingOccupancyFraction=90说明年老代到90%满的时候开始执行对年老代的并发垃圾回收(CMS),这时还 剩10%的空间是5500*10%=550M,所以即使Xmn(也就是年轻代共500M)里所有对象都搬到年老代里,550M的空间也足够了,所以只要满 足上面的公式,就不会出现垃圾回收时的promotion failed
3.Concurrent mode failed 的产生是由于CMS回收年老代的速度太慢,导致年老代在CMS完成前就被沾满,引起full gc,避免这个现象的产生就是调小-XX:CMSInitiatingOccupancyFraction参数的值,让CMS更早更频繁的触发,降低年老代被沾满的可能。
4.java.lang.OutOfMemoryError: PermGen space
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。
在VM args里面增加-XX:MaxPermSize=***m. 持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
java.lang.OutOfMemoryError: PermGen space => the permanent generation is full.
1. If an application loads a very large number of classes, then the size of the permanent generation might need to be increased using the -XX:MaxPermSize option.
2. Interned java.lang.String objects are also stored in the permanent generation.If an application interns a huge number of strings, the permanent generation might need to be increased from its default setting.
5.java.lang.OutOfMemoryError: Java heap space 在JVM中如果98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息
应用举例
1.高并发, 多cpu, 大内存场景
-server
-Xmx3g
-Xms3g
-XX:MaxPermSize=128m
-XX:NewRatio=1 eden/old 的比例
-XX:SurvivorRatio=8 s/e的比例
-XX:+UseParallelGC
-XX:ParallelGCThreads=8
-XX:+UseParallelOldGC 这个是JAVA 6出现的参数选项
-XX:LargePageSizeInBytes=128m 内存页的大小, 不可设置过大, 会影响Perm的大小。
-XX:+UseFastAccessorMethods 原始类型的快速优化
-XX:+DisableExplicitGC 关闭System.gc()
2.针对低延迟的Java虚拟机调优选项
-Xmx512m
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseTLAB
-XX:+CMSIncrementalMode
-XX:+CMSIncrementalPacing
-XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10
如果想最小化执行时间,通常最好是尽量减少垃圾收集次数,这可以通过设置一个更大的堆大小而做到。这将减少垃圾收集次数,但是会使垃圾收集时间变长,而且垃圾收集暂停时间会比使用低延迟选项时长。