Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1734871
  • 博文数量: 391
  • 博客积分: 8464
  • 博客等级: 中将
  • 技术积分: 4589
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-13 15:12
个人简介

狮子的雄心,骆驼的耐力,孩子的执著!

文章分类

全部博文(391)

文章存档

2023年(4)

2018年(9)

2017年(13)

2016年(18)

2014年(7)

2013年(29)

2012年(61)

2011年(49)

2010年(84)

2009年(95)

2008年(22)

分类: Java

2011-06-07 16:55:53

JVM GC调整优化是以个极为复杂的过程,由于各个程序具备不同的特点,如:web和GUI程序就有很大区别(Web可以适当的停顿,但GUI停顿是客户无法接受的),而且由于跑在各个机器上的配置不同(主要cup个数,内存不同),所以使用的JVM GC种类也会不同。接下来,我简单介绍一下如何进行JVM GC调整优化。

首先说一下如何监视JVM GC,你可以使用我以前文章中提到的JDK中的jstat工具,也可以在java程序启动的opt里加上如下几个参数(注:这两个参数只针对SUN的HotSpotVM):

  1. -XX:-PrintGCPrintmessagesatgarbagecollection.Manageable.  
  2. -XX:-PrintGCDetailsPrintmoredetailsatgarbagecollection.Manageable.(Introducedin1.4.0.)  
  3. -XX:-PrintGCTimeStampsPrinttimestampsatgarbagecollection.Manageable(Introducedin1.4.0.) 

当把-XX:-PrintGCDetails加入到javaopt里以后可以看见如下输出:

[GC[DefNew:34538K->2311K(36352K),0.0232439secs]45898K->15874K(520320K),0.0233874secs]
[FullGC[Tenured:13563K->15402K(483968K),0.2368177secs]21163K->15402K(520320K),[Perm:28671K->28635K(28672K)],0.2371537secs]

他们分别显示了JVM GC的过程,清理出了多少空间。第一行GC使用的是‘普通GC’(MinorCollections),第二行使用的是‘全GC’(MajorCollections)。他们的区别很大,在第一行最后我们可以看见他的时间是0.0233874秒,而第二行的FullGC的时间是0.2371537秒。第二行的时间是第一行的接近10倍,也就是我们这次调优的重点,减少FullGC的次数,以为FullGC会暂停程序比较长的时间,如果FullGC的次数比较多。程序就会经常性的假死。当然这只是他们的表面现象,接下来我仔细介绍一下GC,和FullGC(为后面的调优做准备)。

我们知道Java和C++的区别主要是,Java不需要像c++那样,由程序员主动的释放内存。而是由JVM里的GC(GarbageCollection)来,在适当的时候替我们释放内存。JVM GC调整优化的内部工作,即JVM GC的算法有很多种,如:标记清除收集器,压缩收集器,分代收集器等等。现在比较常用的是分代收集(也是SUNVM使用的),即将内存分为几个区域,将不同生命周期的对象放在不同区域里(新的对象会先生成在Youngarea,在几次GC以后,如过没有收集到,就会逐渐升级到Tenuredarea)。在JVM GC收集的时候,频繁收集生命周期短的区域(Youngarea),因为这个区域内的对象生命周期比较短,GC效率也会比较高。而比较少的收集生命周期比较长的区域(OldareaorTenuredarea),以及基本不收集的永久区(Permarea)。

注:Youngarea又分为三个区域分别叫Eden,和俩个Survivorspaces。Eden用来存放新的对象,Survivorspaces用于新对象升级到Tenuredarea时的拷贝。

我们管收集生命周期短的区域(Youngarea)的收集叫GC,而管收集生命周期比较长的区域(OldareaorTenuredarea)的收集叫FullGC,因为他们的收集算法不同,所以使用的时间也会不同。我们要尽量减少FullGC的次数。

接下来介绍一下HotSpotVMGC的种类,GC在HotSpotVM5.0里有四种。一种是默认的叫serialcollector,另外几种分别叫throughputcollector,concurrentlowpausecollector,incremental(sometimescalledtrain)lowpausecollector(废弃掉了)。

简单来说就是throughputcollector和concurrentlowpausecollector:使用多线程的方式,利用多CUP来提高GC的效率,而throughputcollector与concurrentlowpausecollector的去别是throughputcollector只在youngarea使用使用多线程,而concurrentlowpausecollector则在tenuredgeneration也使用多线程。

根据官方文档,他们俩个需要在多CPU的情况下,才能发挥作用。在一个CPU的情况下,会不如默认的serialcollector,因为线程管理需要耗费CPU资源。而在两个CPU的情况下,也挺高不大。只是在更多CPU的情况下,才会有所提高。当然concurrentlowpausecollector有一种模式可以在CPU较少的机器上,提供尽可能少的停顿的模式,见下文。

当要使用throughputcollector时,在javaopt里加上-XX:+UseParallelGC,启动throughputcollector收集。也可加上-XX:ParallelGCThreads=来改变线程数。还有两个参数-XX:MaxGCPauseMillis=和-XX:GCTimeRatio=,MaxGCPauseMillis=用来控制最大暂停时间,而-XX:GCTimeRatio可以提高GC说占CPU的比,以最大话的减小heap。

当要使用concurrentlowpausecollector时,在java的opt里加上-XX:+UseConcMarkSweepGC。concurrentlowpausecollector还有一种为CPU少的机器准备的模式,叫Incrementalmode。这种模式使用一个CPU来在程序运行的过程中GC,只用很少的时间暂停程序,检查对象存活。

在Incrementalmode里,每个收集过程中,会暂停两次,第二次略长。第一次用来,简单从root查询存活对象。第二次用来,详细检查存活对象。整个过程如下:

  1. *stopallapplicationthreads;dotheinitialmark;resumeallapplicationthreads(第一次暂停,初始话标记)  
  2. *dotheconcurrentmark(usesoneprocesorfortheconcurrentwork)(运行是标记)  
  3. *dotheconcurrentpre-clean(usesoneprocessorfortheconcurrentwork)(准备清理)  
  4. *stopallapplicationthreads;dotheremark;resumeallapplicationthreads(第二次暂停,标记,检查)  
  5. *dotheconcurrentsweep(usesoneprocessorfortheconcurrentwork)(运行过程中清理)  
  6. *dotheconcurrentreset(usesoneprocessorfortheconcurrentwork)(复原) 

当要使用Incrementalmode时,需要使用以下几个变量:

  1. -XX:+CMSIncrementalModedefault:disabled启动i-CMS模式(mustwith-  
  2. XX:+UseConcMarkSweepGC)  
  3. -XX:+CMSIncrementalPacingdefault:disabled提供自动校正功能  
  4. -XX:CMSIncrementalDutyCycle=default:50启动CMS的上线  
  5. -XX:CMSIncrementalDutyCycleMin=default:10启动CMS的下线  
  6. -XX:CMSIncrementalSafetyFactor=default:10用来计算循环次数  
  7. -XX:CMSIncrementalOffset=default:0最小循环次数(Thisisthepercentage(0-  
  8. 100)bywhichtheincrementalmodedutycycleisshiftedtotherightwithintheperiod  
  9. betweenminorcollections.)  
  10. -XX:CM***pAvgFactor=default:25提供一个指导收集数 


SUN推荐的使用参数是:

  1. -XX:+UseConcMarkSweepGC\  
  2. -XX:+CMSIncrementalMode\  
  3. -XX:+CMSIncrementalPacing\  
  4. -XX:CMSIncrementalDutyCycleMin=0\  
  5. -XX:CMSIncrementalDutyCycle=10\  
  6. -XX:+PrintGCDetails\  
  7. -XX:+PrintGCTimeStamps\  
  8. -XX:-TraceClassUnloading 

注:如果JVM GC中使用throughputcollector和concurrentlowpausecollector,这两种垃圾收集器,需要适当的挺高内存大小,以为多线程做准备。JVM GC调整优化到此结束。

关于Heap的几个参数设置:
       说了Heap的有限资源问题以后,就来看看如何通过配置去改变JVM对于Heap的分配。下面所说的主要是对于Java Heap的分配,那么在申请了Java Heap以后,剩下的可用资源就会被使用到Native Heap。
       Xms: java heap初始化时的大小。默认情况是机器物理内存的1/64。这个主要是根据应用启动时消耗的资源决定,分配少了申请起来会降低启动速度,分配多了也浪费。
       Xmx:java heap的 最大值,默认是机器物理内存的1/4,最大也就到1G。这个值决定了最多可用的Java Heap Memory,分配过少就会在应用需要大量内存作缓存或者零时对象时出现OOM的问题,如果分配过大,那么就会产生上文提到的第二类OOM。所以如何配置 还是根据运行过程中的分析和计算来确定,如果不能确定还是采用默认的配置。
       Xmn:java heap新 生代的空间大小。在GC模型中,根据对象的生命周期的长短,产生了内存分代的设计:青年代(内部也分成三部分,类似于整体划分的作用,可以通过配置来设置 比例),老年代,持久代。每一代的管理和回收策略都不相同,最为活跃的就是青年代,同时这部分的内存分配和管理效率也是最高。通常情况下,对于内存的申请 优先在新生代中申请,当内存不够时会整理新生代,当整理以后还是不能满足申请的内存,就会向老年代移动一些生命周期较长的对象。这种整理和移动会消耗资 源,同时降低系统运行响应能力,因此如果青年代设置的过小,就会频繁的整理和移动,对性能造成影响。那是否把年青代设置的越大越好,其实不然,年青代采用 的是复制搜集算法,这种算法必须停止所有应用程序线程,服务器线程切换时间就会成为应用响应的瓶颈(当然永远不用收集那么就不存在这个问题)。老年代采用 的是串行标记收集的方式,并发收集可以减少对于应用的影响。
       Xss:线程堆栈最大值。允许更多的虚拟内存空间地址被Java Heap使用。
以下是sun公司的性能优化白皮书中提到的几个例子:
1.对于吞吐量的调优。机器配置:4G的内存,32个线程并发能力。
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
       -Xmx3800m -Xms3800m 配置了最大Java Heap来充分利用系统内存。
       -Xmn2g 创建足够大的青年代(可以并行被回收)充分利用系统内存,防止将短期对象复制到老年代。
    -Xss128 减少默认最大的线程栈大小,提供更多的处理虚拟内存地址空间被进程使用。
    -XX:+UseParallelGC 采用并行垃圾收集器对年青代的内存进行收集,提高效率。
    -XX:ParallelGCThreads=20 减少垃圾收集线程,默认是和服务器可支持的线程最大并发数相同,往往不需要配置到最大值。
2.尝试采用对老年代并行收集
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-Xmx3550m -Xms3550m 内存分配被减小,因为ParallelOldGC会增加对于Native Heap的需求,因此需要减小Java Heap来满足需求。
-XX:+UseParallelOldGC 采用对于老年代并发收集的策略,可以提高收集效率。
3.提高吞吐量,减少应用停顿时间
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC 选择了并发标记交换收集器,它可以并发执行收集操作,降低应用停止时间,同时它也是并行处理模式,可以有效地利用多处理器的系统的多进程处理。
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=31 表示在青年代中Eden和Survivor比例,设置增加了Survivor的大小,越大的survivor空间可以允许短期对象尽量在年青代消亡。
-XX:TargetSurvivorRatio=90 允许90%的空间被占用,超过默认的50%,提高对于survivor的使用率。
例如:
 JAVA_OPTS="$JAVA_OPTS -server -Xms4096m -Xmx4096m -XX:PermSize=256m -XX:MaxNewSize=512m -XX:MaxPermSize=512m -Djava.awt.headless=true -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSIncrementalMode -XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=10 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:-TraceClassUnloading"
 
tomcat 连接器优化:
 
                   connectionTimeout="20000"  minSpareThreads="100"
               maxSpareThreads="500" maxKeepAliveRequests="100"
               enableLookups="false" acceptCount="100"
               redirectPort="8443" />

tomcat配置文件server.xml中的配置中,和连接数相关的参数有:
maxThreads

Tomcat
使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数。默认值150
acceptCount

指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。默认值10
minSpareThreads

Tomcat
初始化时创建的线程数。默认值25
maxSpareThreads

一旦创建的线程超过这个值,Tomcat就会关闭不再需要的socket线程。默认值75
enableLookups

是否反查域名,默认值为true。为了提高处理能力,应设置为false
connnectionTimeout

网络连接超时,默认值60000,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。
maxKeepAliveRequests

保持请求数量,默认值100
bufferSize

输入流缓冲大小,默认值2048 bytes
compression

压缩传输,取值on/off/force,默认值off
其中和最大连接数相关的参数为maxThreadsacceptCount。如果要加大并发连接数,应同时加大这两个参数。web server允许的最大连接数还受制于*作系统的内核参数设置,通常Windows2000个左右,Linux1000个左右。
 
阅读(1361) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~