JVM GC Collector工做原理及优化

JVM 调优主要是调整GC以及一些执行参数:java

目标:web

堆不要太大,否则单次GC的时间过长致使服务器没法响应的问题算法

压力测试的时候TPS平稳安全

尽可能避免full GC服务器

检查是否用了并行的垃圾回收器多线程

 

参数:并发

-server执行,开启优化oracle

采用并行gc collector, -XX:+UseParallelGC +XX:+UseParallelOldGC +XX:+UseConcMarkSweepGCjvm

-Xmx不要太大,否则单次gc的过程可能太长,大内存机器能够采用多个实例的方式性能

-Xms不要过小,否则jvm须要屡次调整堆的大小,增长gc次数,影响了启动性能。

同理-XX:MaxPermSize

-XX:NewRatio新生代与老年代的比例要合适,这个须要看应用类型,通常新生代的对象很快就会被GC掉了。

线程池的大小要合适,线程上下文切换也是很耗资源的。

不要去调整一些你不彻底了解的参数。

还有-Xverify:none能够加快启动速度,但字节码问题查错很麻烦。

 

字节码调优应该避免吧,容易触发一些jvm自己的bug,这些参数缺乏实际场景的测试。

CMS垃圾收集器的相关参数,请读取相关资料。貌似CMS不会去移动对象的去使得空间更加紧凑。

G1垃圾收集器的工做原理,G1能够Compact Heap Region以减小内存碎片问题。

 

相关连接:一个Oracle jvm部门里面的员工的blog: https://blogs.oracle.com/jonthecollector/ 里面的文章颇有价值。

 

一些命令:

// 监控gc的状况,每隔2秒输出一次gc的信息
jstat -gcutil pid 2000

// 每列的语言以下
S0(Survivor0) S1(Survivor1) E(Eden) O(Old) P(Perment) YGC (Young GC Count) YGCT(Young GC Time) FGC(Full GC Count) FGCT(Full GC Time)

 

 Memory Management In The Java HotSpot Virtual Machine

一、为何要使用自动内存管理

为了消除手动内存管理带来的复杂性以及内存泄漏,dangling引用,内存碎片等一系列问题

二、动态内存分配是一个比较复杂的工做,由于它要使用内存的分配及释放足够快,同时还要考虑内存碎片问题。

三、垃圾回收器必须尽可能少使得程序暂停,同时也须要在 回收耗时、空间大小,回收次数这几个方面取得一个平衡,另外还须要控制内存碎片。

四、垃圾回收器必须是可扩展,它不该该成为程序性能的瓶颈,它应该能够在多线程/多CPU的环境并行得执行。

五、垃圾回收器最好可以并发地进行内存回收工做,并发状况下,堆被划分红几个区域,这些区域会被并发地进行回收,由此减小了回收工做所引发的程序的暂停。

六、Compacting vs Non-Compacting vs Copying

七、垃圾回收器性能评判的几个指标:GC时间占程序运行时间的比重,非GC时间占比,GC引发的暂停时间,回收的频率,检测到垃圾对象的速度,回收器工做消耗的内存大小。

八、内存分代回收,比较流行分为young generation(新生代)及old generation(老年代),大多数分配的对象不会存活得过久,一般只有一小部分对象能够进化到老年代。所以新生代的区域回收得比较频繁,而老年代空间经过占用比较多的空间,所以各个区域会使用不一样的算法进行垃圾回收。好比-XX:NewRatio=4表示 young generation : old generation = 1 : 4。在HotSpot Virutial Machine中,内存被分为三个区域,young generation, old generation和permenent generation,大部分对象都是在新生代进行分配的,有些对象会直接在老年代进行分配。新生代的空间由Eden生两个survivor区域来组成,一个survivor用来存放至少存活超过一次young generation GC的对象,而另外一个survivor空间是空的,直接下一次回收的时候会被使用。一个suvivor与Eden的大小比值能够用-XX:SurvivorRatio=n来表示(1/n=survivor : Eden)

当old generation因塞满而没法存放young generation升级上来的对象时,将触发full GC,这时大部分的收集器会用老年代的算法去GC整个Heap(除了CMS Collector外, CMS Collector的老年代算法没法回收新生代区域)。

 

快速分配,在大片链接的内存块中进行分配内存的效率是很高的,能够利用bump-the-pointer技术来进行分配,分配器会记住下一次分配的起点。对于多线程的程序来讲,内存分配操做须要是线程安全的,若是使用全局锁的话这会下降性能并形成性能瓶颈,相应的HotSpot采用一种叫作Thread-Local Allocation Buffers的技术(线程本身的内存分配缓冲区),使得减小获取全局锁的操做,一般TLAB占用大概1%的Edgen的大小。结合TLAB和bump-the-pointer技术,一般分配一个对象空间只须要10条本地指令。

 

Serial Collector

young generation使用Serial Collector进行回收的过程

当使用Serial Collector时候,young generation和old generation的回收工做都是使用单个CPU线性执行的,回收过程当中将stop-the-world。下图展现了Serial Collector对young generation进行回收时过程,Eden区域存活的对象被复制到那个空的Survivor块(图中用to标记),若是对象太大超出了Survivor的大小,那么它将直接被copy到old generation区域,而非空的survivor(图中用from标记)中仍然年轻的对象也被复制到空的survivor中(图中用to标记),而相对比较“老”的对象则被复制到old generation区域中。

注意:若是"To" survivor被塞满了话,Eden和"From"Survivor区域尚未被copy的对象将直接被复制到old generation中(不管它们存活多少了多少代)。

在完成了对young generation的回收以后,young generation中Eden区域和"From" survivor都被被清空,只有"To" survivor中有存活的对象。这时,"From"和"To" Survivor将对换,以下图所示:

注意,存在old generation引用young generation对象的状况,为了不进行young gc的时候扫描old generation,老年代对new generation的引用被记录在一个叫作card table的cache中。

old generation使用Serial Collector进行回收的过程:

当使用Serial Collector对old generation和permenent generation进行回收的时候,它将使用一种mark-sweep-compact的回收算法:

Mark阶段:Collector检测那些对象仍然存活。

Sweep阶段:Collector扫描整个old generation或者permenent generation,

Compact阶段:Collector将存活的对象“滑动”到old generation的首部,全部连续的空间放在old generation的尾部,这样方便利用bump-the-pointer来实现快速地分配对象。以下图所示:

何时应该使用Serial Collector。

大部分以Client-Style(java -client)运行的程序都使用这种收集器,这类程序对回收引种的程序暂停时候不敏感。在如今的硬件条件下,Serial Collector可以管理64MB大小的堆空间(如今应该能够256MB了吧)。

在JavaSE5中,运行非server-class的机器上默认使用Serial Collector,但用户可使用-XX:+UseSerialGC命令参数来指定使用Serial Collector。

Parallel Collector(也被称为Throughput Collector)

现在,不少程序运行在拥有大内存多CPU的机器上面,Parallel Collector就是为了在垃圾回收过程当中充分利用CPU资源而开发的。

使用Parallel Collector对young generation进行垃圾回收的过程:

Parallel Collector使用一种相似于Serial Collector对young generation回收的算法的并行版本,回收时它仍然会stop-the-world,但在回收的过程当中它并行地使用多个cpu并行地执行,由些来减小垃圾回收所占用的时候并提高程序运行时间的占比。

使用Parallel Collector对old generation进行回收

Parallel Collector使用了与Serial Collector一样的mark-sweep-compact的回收算法。

何时使用Parallel Collector

运行在多cpu以及对中止时间不敏感的程序能够从使用parallel collector中受益,不频繁,耗时较长的针对old generation区域回收的仍然会发生, 批量处理,计费,工资以及科学计算这类程序比较适合使用Parallel Collector。

JavaSE5中,运行server-class的机器上默认使用Parallel Collector,用户可使用-XX:UseParallelGC命令参数来显示指定使用Parallel Collector

 

Parallel Compacting Collector

Parallel Compacting Collector在JavaSE5.0 update 6被引入,与Parallel Collector不一样的是,它使用了一种新的算法来对old generation进行回收,最终Parallel Compacting Collector将取代Parallel Collector

回收young generation时,Parallel Compacting Collector使用了与Parallel Collector一样的算法。

回收old generation和permenent generation时,Parallel Compacting Collector仍然会stop-the-world,但在整理存活对象的时候大部分是并行的。Parallel Compacting Collector使用三个阶段进行,

1、young/old/permenent generation区域被划分红几个固定大小的区域

2、marking阶段,程序中仍然能够引用到的存活的对象被划分给几个garbage collection threads中,而后mark工做是并发执行地,当一个对象被标记为存活的时候,它所在的regioin的大小将会被更新。

3、Summary阶段,经过前几回的收集,generation空间的首部会存活的对象会比较密集,经过compacting能回收的空间比较少,所以不值得在上面进行compacting,因此summary阶段所作的第一件事就是检验regions的密度,从最左边开始,直到碰到一个密度比较小,值得花时间去compacting的region,而后从这个region开始,compacting右边的region。summary阶段计算并存放被compacting region的新的首地址(这个阶段并无真正地去Compacting)。注意:summary段是单线程执行的,尽管它能够实现为并发执行。但事实代表并发执行的

4、compacting阶段,在这个阶段中,利用上一阶计算出来的Compacting信息,各个线程能够独立地往region移动对象。Compacting完成以后,堆空间的后部将释放出一片连续的空间。

何时使用Parallel Compacting Collector

在Parallel Collector的基础上,Parallel Compacting Collector进一步减小了因为回收old generation所消耗的时候,进一步知足对垃圾回收引发的暂停时间 比较敏感的程序。但须要注意的是,Parallel Compacting Collector 能够不适合那些运行在大型机/刀片机的程序,这种机器上是不容许单独一个程序占用几个cpu过长时间,在这种环境下能够考虑利用参数-XX:ParallelGCThread=n,或者选择另外的垃圾回收器。

须要使用-XX:+UseParallelOldGC来显式指定使用Parallel Compacting Collector,这个参数的名字有点奇怪,这里的"Old"是指old generation。

 

Concurrent Mark-Sweep (CMS) Collector

对于不少应用来讲,应用运行时间占比(throughput)没有响应时间这么重要,young generation区域的回收一般不会形成太长时间的暂停。可是old generation的回收,尽管不是很频繁,但一般会强制应用暂停比较长的时间。为了解决这个问题,HotSpot JVM 引入了一个叫作concurrent mark-sweep (CMS)的收集器,也叫作low-latency collector(低延迟回收器)

CMS回收young generation的过程:

CMS与Parallel Collector使用一样的方式回收young generation

CMS回收old generation的过程:

CMS在回收old generation大多数时候都是在程序运行时并发地执行,在开始一次完整的回收以前,CMS须要暂停一下程序(stop-the-world),这个过程叫作初始标记(Initial Mark),这个过程当中查找程序代码中能够直接引用到的对象(一般是线程栈上的对象引用),而后在并发标记阶段,CMS去标记全部可达的对象,由于这个工做是并发进行的,应用同时也在更新一些字段的引用,因此在并发标记以后须要来一个stop-the-world,将新产生的对象标记完整,这个过程被称为remark,remark比initial mark更加耗时,因此通常使用多个线程并发地执行来提交效率。

在remark结束以后,全部的可达的对象都被标记了,而后接下来的并发扫描阶段将回收垃圾,下图阐述了CMS Collector与Serial  mark-sweep-compact Collector之间的差异:

 由于有一些工做,好比在remark阶段从新遍历对象,增长了collector的工做量,因此CMS回收时占用的CPU和内存资源也更多,但它减小了应用的中止时间。

须要注意的是,CMS collector是惟一一个不会去compact(整理)内存的收集器,以下图所示,这节省了一些时间,但由于这些空间并不链接,bump-the-pointer也不奏效了,所以它须要使用一个free列表去记录可以使用的空间,而后在分配时去查找这个list。所以分配空间的操做效率相对要低一些,同时,这也会形成回收young generation的一些负担,所以回收young generation时须要从young generation里面复制一些存活的对象到old generation中,内存碎片的风险也增长了。

另外,与其它收集器不一样的是,当old generation满了以后,它并不会发起一个old generation的回收,相反,它会尝试在尚未满的时候发起一次回收(由于在concurrent mark阶段程序是并发运行地),以便可以用完以前可以完成回收,不然它将转为使用与parallel collector 和serial collector同样stop-the-world方法去回收这部分空间。为了不这一点,CMS Collector定时记录了一些垃圾回收的数据,好比回收的速率,而后在恰当的时候触发回收操做,避免在用完的时候再进行回收。另外,当old generation占用超过必定程度以后,CMS Collector也会去发起一次回收操做,能够用-XX:CMSInitiatingOccupanyFraction=n,n是old generation大小的占比,默认是68。

总之,CMS可以减小大部分程序因为回收工做而被暂停的时间 ,但结果的代价是:回收young generation变慢了,程序运行时间占比降低,以及更大的堆空间消耗。

增量式的回收

CMS Collector能够运行在增量回收的模式下,这种模式下,young generation回收过程的时候被分为几个小块的时间段。以减小stop-the-world时间。

CMS Collector适合那些对暂时时间比较敏感,容许GC操做并发地使用CPU,并且有大量存活对象的应用,好比web server。

必须使用-XX:+UseConcMarkSweepGC来显示指定使用CMS Collector,若是须要运行在增量模式下,必须使用-XX:+CMSIncrementalMode参数。

 

 默认堆大小

在-server模式下,jvm的默认初始heap大小是1/64的物理内存(最多1GB),默认最大堆大小是1/4物理内存大小(最大1GB)

-client模式下,默认4MB初始heap大小及64MB的最大堆大小。固然这些均可以经过命令参数进行覆盖。

 

其它参数

另外还可使用-XX:MaxGCPauseMillis=n来指定GC形成的最大停顿时间,这个时间不必定能完成,不能完成的话,Collector会在堆未占满的状况触发回收操做。

另外也可使用-XX:GCTimeRatio=n来设置GC时间占比(GC:程序运行时间),好比GCTimeRatio=4的状况下,GC时间将最大占用20%的时间。和-XX:MaxGCPauseMillis同样,若是不能知足要求,Collector将在geneartion占满以前触发回收操做。

相关文章
相关标签/搜索