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)
一、为何要使用自动内存管理
为了消除手动内存管理带来的复杂性以及内存泄漏,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和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中。
当使用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来实现快速地分配对象。以下图所示:
大部分以Client-Style(java -client)运行的程序都使用这种收集器,这类程序对回收引种的程序暂停时候不敏感。在如今的硬件条件下,Serial Collector可以管理64MB大小的堆空间(如今应该能够256MB了吧)。
在JavaSE5中,运行非server-class的机器上默认使用Serial Collector,但用户可使用-XX:+UseSerialGC命令参数来指定使用Serial Collector。
现在,不少程序运行在拥有大内存多CPU的机器上面,Parallel Collector就是为了在垃圾回收过程当中充分利用CPU资源而开发的。
Parallel Collector使用一种相似于Serial Collector对young generation回收的算法的并行版本,回收时它仍然会stop-the-world,但在回收的过程当中它并行地使用多个cpu并行地执行,由些来减小垃圾回收所占用的时候并提高程序运行时间的占比。
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在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 Collector的基础上,Parallel Compacting Collector进一步减小了因为回收old generation所消耗的时候,进一步知足对垃圾回收引发的暂停时间 比较敏感的程序。但须要注意的是,Parallel Compacting Collector 能够不适合那些运行在大型机/刀片机的程序,这种机器上是不容许单独一个程序占用几个cpu过长时间,在这种环境下能够考虑利用参数-XX:ParallelGCThread=n,或者选择另外的垃圾回收器。
须要使用-XX:+UseParallelOldGC来显式指定使用Parallel Compacting Collector,这个参数的名字有点奇怪,这里的"Old"是指old generation。
对于不少应用来讲,应用运行时间占比(throughput)没有响应时间这么重要,young generation区域的回收一般不会形成太长时间的暂停。可是old generation的回收,尽管不是很频繁,但一般会强制应用暂停比较长的时间。为了解决这个问题,HotSpot JVM 引入了一个叫作concurrent mark-sweep (CMS)的收集器,也叫作low-latency collector(低延迟回收器)
CMS与Parallel Collector使用一样的方式回收young 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占满以前触发回收操做。