java性能优化权威指南--垃圾收集器入门

目录

1、垃圾收集器概述

1)、分代垃圾收集器

2)、GC算法

3)、选择GC算法

2、GC调优基础

1)、调整堆大小

2)、代空间的调整

3)、永久代和元空间的调整

4)、控制并发

5)、自适应调整

3、垃圾回收工具


五、垃圾收集器入门
page81-page102

主流的四个垃圾收集器:
Serial收集器(常用于单CPU环境)
throughput(或者parallel)收集器
concurrent收集器(CMS)
G1收集器

垃圾收集分为两步:查找不再使用的对象,然后释放这些对象所管理的内存。

jvm中的线程被分为应用线程和处理垃圾收集的线程;
在垃圾回收的过程中,对象的内存地址会发生变化,因此这个过程中任何应用线程都不应该再访问对象。
所有应用线程都停止运行所产生的停顿称为时空停顿(stop-the-world);

1、垃圾收集器概述

1)、分代垃圾收集器

所有的GC算法都是将堆划分成老年代和新生代;
所有的GC算法在清理新生代对象时,都是使用了“时空停顿(stop-the-world)”方式的垃圾收集方法,通常这是一个能较快完成的操作;

分代设计的优势:
1、由于新生代只是堆的一部分,与处理整个堆相比,处理新生代的速度更快。即停顿的时间更短但是频率会更高。
2、源于新生代中对象的分配方式。新生代的minorGC,对象分配在Eden空间,垃圾收集时,新生代Eden空间被清空,原来Eden空间中的对象要么被移走,要么被回收;所有存活的对象要么被移动到另外一个survivor空间,要么被移动到老年代。相当于对新生代在垃圾回收时进行了一次压缩整理。

当老年代满的时候会触发FullGC
简单的做法是直接停掉所有的应用线程,找出不在使用的对象,对其进行回收,接着对堆空间进行整理,这通常导致应用程序线程长时间的停顿;另外还有可能在应用线程运行的同时找出不再使用的对象,如(CMS和G1收集器),他们不需要停止应用线程就能找到不在使用的对象,因此被称为低停顿收集器,其代价是消耗更多的CPU

2)、GC算法

  • Serial收集器(常用于单CPU环境),是client型虚拟机的默认垃圾收集器

最简单的一种,使用单线程清理堆,包括minorGC和FullGC,通过-XX:+UseSerualGC启用serial收集器,关闭的时候不能直接-XX:-UseSerualGC,需要指定另外一种收集器

 

  • throughput(或者parallel)收集器(特点:最大化应用程序的吞吐量,但是可能遭遇较长的停顿)

是server型虚拟机默认的垃圾收集器。对minorGC和fullGC都是使用多线程的方式,也被称为parallel收集器;
在minorGC和fullGC时会暂停所有的应用线程,同时在fullgc过程中会对老年代空间进行压缩整理。
可以使用-XX:+UseParallelGC、-XX:+UseParallelOldGC标志启用收集器

  • concurrent收集器(CMS  concurrent mark sweep)

特点:

能够在应用线程运行的同时并行地对老年代进行垃圾回收,该算法能避免应用程序发生fullGC,缺点消耗CPU
设计的初衷:

消除Serial收集器和throughput(或者parallel)收集器FullGC周期中的长时间停顿。
在minorGC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收,和throughput不同的是,CMS使用的不是XX:+UseParallelGC算法而是XX:+UseParNewGC算法;
在fullGC的时候,不在暂停应用线程,而是使用若干个后台线程定期地对老年代进行扫描,及时回收其中不在使用的对象,这种算法使得CMS成为一个低延迟的收集器,
即:应用线程只在minorGC以及后台线程扫描老年代时发生极其短暂的停顿,和throughput收集器比起来短的多。

缺点:
消耗更高的CPU资源;
后台线程不在进行任何的压缩整理,会出现堆变得碎片化。
如何CMS无法获得足够的CPU资源或者堆过度碎片化,CMS就蜕化到serial收集器的行为:暂停所有的应用线程,使用单线程回收、整理老年代空间。这之后又恢复到并发运行、再次启动后台线程(直到下一次变得碎片化)。
通过-XX:+UseConcMarkSweepGC   -XX:+UseParNewGC标志启用CMS垃圾回收器,默认情况下是关闭的。

  • G1收集器

特点:

也是能够在应用线程运行的同时并行地对老年代进行垃圾回收,在某种程度上能够减少发生fullGC的风险,G1的设计理念使得它比CMS更不容易遭遇fullGC,缺点消耗CPU
设计初衷:

为了尽量缩短处理超大堆(大于4G)时产生的停顿。
minorGC也是暂停所有的应用线程,使用多线程的方式完成;
设计的原理把堆内存分区(仍旧是分代),在fullGC时,不需要暂停应用线程,由于老年代被分到不同的区域,
G1收集器通过将对象从一个区域复制到另外的一个区域,完成对象的清理工作,这意味着在正常处理的过程中,G1收集器实现了堆的压缩整理(至少是部分整理)。
因此使用G1收集器不太容易发生碎片化---虽然这种问题无法避免。
通过标志-XX:+UseG1GC启用G1垃圾收集器,默认是关闭的。

缺点:
消耗CPU资源

system.gc()

一般情况下不建议使用手动触发GC
可以通过参数-XX:+DisableExplicitGC显式地禁止,默认情况下是关闭的。
不过在一些情况下是需要用到的,比如在做性能测试时,通常在获取堆转储之前强制进行一次fullGC。

3)、选择GC算法

serial收集器适用于内存使用少于100M的场景,这种场景其他三个收集器都发挥不了太大作用。
大多数是在throughput和concurrent(CMS和G1)收集器之间做选择。

throughput收集器和concurrent收集器如何选择?

  • 使用throughput收集器处理应用程序线程的批量任务能最大程度地利用CPU的处理能力,通常获得更好的性能;
  • 如果批量任务没有使用机器上所有可用的CPU资源,那么切换到concurrent收集器往往能取得更好的性能。
  • 衡量标准是响应时间或吞吐量,在throughput收集器和concurrent收集器之间做选择的依据主要是有多少空闲CPU资源能用于后台的并发线程;
  • 通常情况下,throughput收集器的平均响应时间比concurrent收集器要差,但是在90%响应时间或者99%响应时间这几项指标上,throughput收集器比concurrent收集器要好一些;
  • 使用throughput收集器会超负荷运行大量GC时,切换到concurrent收集器通常能够获得更低的响应时间。


CMS和G1如何选择?(都会存在并发模式失效的情况)

  • 选择concurrent收集器时,如果堆比较小,推荐使用CMS收集器;(    CMS在fullGC时需要扫描这个老年代,堆大的话比较耗时)
  • G1的设计使得它能够在不同的分区处理堆,因此,它的扩展性更好,比CMS更易于处理超大堆的情况;

2、GC调优基础

1)、调整堆大小

  • jvm会根据其运行的机器,尝试估算合适的最大、最小堆的大小;
  • 除非应用程序需要比默认值更大的堆,否则在进行调优时,尽量考虑通过调整GC算法的性能目标,而非微调堆的大小来改善程序性能;

-Xms4096m  设置堆大小的初始值
-Xmx4096m  设置堆大小的最大值
如果堆的初始值小于最大值,在程序运行的过程中,jvm会自适应调整堆的大小,以使得GC的效率最好.但是如果自己确切知道自己需要多大的内存,将初始值和最大值设置为一样,这样省去jvm估算堆是否需要调整大小的消耗,GC的效率也会稍微提升.

为什么不把堆设置得越大越好?
1、GC停顿消耗的时间取决于堆的大小,这样虽然停顿的频率变少,但是他们持续的时间会让程序的整体性能变慢;
2、堆的大小受物理机的限制。操作系统使用虚拟内存机制管理物理内存,虽然一台机器的物理内存可能有8G,不过操作系统使得你会觉得有更多可用的内存。
当一旦设置的jvm堆大于物理内存,就会出现内存和磁盘之间进行数据的交换(swap),并且有可能使得fullGC时的并发模式失效。
总结:调整堆的大小的首要原则是不要将堆的大小设置得比物理机还大(包括一台机器上的多个jvm实例,其所有堆的和不能操作物理内存),此外,通常情况下还需要预留至少1G的内存空间。

2)、代空间的调整

 

  • 整个堆范围内,不同代的大小划分是由新生代所占用的空间控制的;
  • 新生代的大小会随着整个堆大小的增大而增大,但这也是随着整个堆的空间比率波动变化的(依据新生代的初始值和最大值)

所有用于调整代空间的命令行标志调整的都是新生代空间,新生代剩下的所有空间都是老年代占用。
-XX:NewRatio=N 设置新生代和老年代的空间占用比率(默认值是2,即占整个堆空间的1/3,计算公式:新生代大小=堆初始大小/(1+NewRatio))
-XX:NewSize=N 设置新生代空间的初始大小
-XX:MaxNewSize=N 设置新生代空间的最大大小
-XmnN 将newsize和maxsize设置为同一个值

优先级 NewSize的优先级高于NewRatio,这两种方式的设置,新生代的大小都会随着堆总体大小的变化而变化;
使用-XmnN 可以将新生代的大小固定在一个值,而不随堆总体的变化而变化

新生代过小或者过大的特点?
过小:频繁的进行minorGC
过大:垃圾收集器发生的频率比较低,从新生代晋升到老年代的对象也更少,但是这样的分配方式使得老年代相对比较小,比较容易被填满,会更频繁的触发fullGC

3)、永久代和元空间的调整

 

  • 永久代或元空间保存着类的元数据(而非类本体数据),它以分离的堆的形式存在;
  • 典型应用程序在启动后不久需要载入新的类,这个区域的初始值可以依据所有类都加载后的情况设置,使用优化的初始值能够加速启动的过程;
  • 开发中的应用服务器(或者任何需要频繁重新载入类的环境)上经常能够碰到由于永久代或元空间耗尽触发的fullGC,这时老的元数据会被丢弃回收;

永久代(32位机器默认值64M,64位默认82M)
-XX:PermSize=N
-XX:MaxPermSize=N

元空间(默认没有限制)
-XX:MetaspaceSize=N
-XX:MaxMetaspaceSize=N


4)、控制并发

总的GC线程数目计算公:ParallelGCThreads = 8 + ((N-8)*5 / 8)

 

  • 几乎所有的垃圾收集算法中基本的垃圾回收线程数都是依据机器上的CPU数目计算得出的;
  • 当多个jvm运行于同一台物理机上时,依据公式计算出的线程数都可能高,必须进行优化(减少);

java性能优化权威指南--垃圾收集器入门_第1张图片

 

5)、自适应调整

-XX:-UseAdaptiveSizePolicy关闭全局范围内自适应调整功能(默认是开启的)

 

  • jvm在堆的内部如何调整新生代及老年代的百分比是由自适应调整机制控制的;
  • 通常情况下,我们应该开启自适应调整,因为垃圾回收算法依赖于调整后的代的大小来达到它停顿时间的性能目标;
  • 对于已经精细调优过的堆,关闭自适应调整能获得一定的性能提升;

 

3、垃圾回收工具

 

  • GC日志是分析GC相关问题的重要线索;我们应该开启GC日志标志(即使是在生产服务器上);
  • 使用printGCDetails标志能够获得更详细的GC日志信息;
  • 使用jstat能动态地观察运行程序的垃圾回收操作;

jstat   -gcutil process_id 1000

java性能优化权威指南--垃圾收集器入门_第2张图片

java性能优化权威指南--垃圾收集器入门_第3张图片

 

 

 

 

你可能感兴趣的:(读书笔记)