java 全局变量 内存不回收_跟我学Java内存管理----JMM精华5(垃圾回收算法、分代垃圾回收)...

1 垃圾回收算法及分代垃圾收集器

1.1 GC判断对象是否"存活

1.引用计数器算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值加1;当引用失效时,计数器的值减;当该对象的计数器的值为0时,标志该对象失效。

引用计数法的缺点?

很难解决对象之间的循环引用问题。

2.跟搜索算法

基本思路:通过一系列的名为“GCRoots”的对象作为起始点,从这些节点开始向下搜索,搜索过的路径称为引用链,当一个对象到GCRoots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达)时,则证明对象是不可用的。

1.2 常见垃圾回收算法

所有垃圾回收算法都需要解决或优化---需要暂停整个应用这个问题(暂停所有用户线程)--

垃圾回收算法有很多,这里只列举了4个。

1.2.1 引用计数(Reference Counting)

比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。

1.2.2 复制(Copying)

此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。简图如下:

3af351eef908b2299deab15cb2611f8f.png

beforeGC图中,左边是from区,右边是to区(橘色部分,空闲空间)

afterGC图中,右边是from区,左边是to区(橘色部分,空闲空间)。

显然经过了一次复制后,进行了碎片整理。

1.2.3 标记-清除(Mark-Sweep)

此算法执行分两阶段:第一阶段从引用根节点开始标记所有被引用的对象(不会被回收);第二阶段遍历整个堆,把未标记的对象(没有引用)清除。此算法需要暂停整个应用,同时,会产生内存碎片。简图如下:

258b3f0f671b4ee52ddf74bb66542b27.png

蓝色表示没有引用指向的对象,可以被回收。

白色表示空闲空间。

红色表示有引用指向的对象,不会被回收。

1.2.14 标记-整理(Mark-Compact)

此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。其本质就是对标记清除算法的一个优化,简图如下:

25f09b9c1c99547b88c19b7d48cbb1a8.png

1.3 垃圾收集器的分类

所有的GC都会触发全世界的暂停(stop-the-world),停止应用程序的线程,不过这个过程非常短暂。

1.3.1 Minor GC

Minor GC,指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Minor GC非常频繁,一般回收速度也比较快。当Eden空间不足以为对象分配内存时,会触发Minor GC。

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

当年轻代堆空间紧张时会被触发;相对于全收集而言,收集间隔较短;

1.3.2 Full GC

Full GC,指发生在老年代的GC,出现了Full GC一般会伴随着至少一次的Minor GC(老年代的对象大部分是Minor GC过程中从新生代进入老年代),比如:分配担保失败。Full GC的速度一般会比Minor GC慢10倍以上。当老年代内存不足或者显式调用System.gc()方法时,会触发Full GC。

当老年代或者持久代堆空间满了,会触发全收集操作。

可以使用System.gc()方法来显式的启动全收集(不一定立即开始收集)。

全收集一般根据堆大小的不同,需要的时间不尽相同,但一般会比较长。

1.4 新生代垃圾回收执行机制

在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)。

1.4.1 串行GC(SerialGC)

Serial收集器是Hotspot运行在Client模式下的默认新生代收集器(已经过时了,了解即可), 它的特点是:只用一个CPU(计算核心)/一条收集线程去完成GC工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(“Stop The World” -后面简称STW)。可以使用-XX:+UseSerialGC打开。

虽然是单线程收集, 但它却简单而高效, 在VM管理内存不大的情况下(收集几十M~一两百M的新生代), 停顿时间完全可以控制在几十毫秒~一百多毫秒内。

9fcdf1e028c1b561c9f01065e4565391.png

上图中的安全点指的是stop-the-world。

1.4.2 并行GC(ParNew)

ParNew收集器其实是前面Serial的多线程版本, 除使用多条线程进行GC外, 包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是VM启用CMS收集器-XX: +UseConcMarkSweepGC的默认新生代收集器)。

由于存在线程切换的开销, ParNew在单CPU的环境中比不上Serial, 且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial。 但随着可用的CPU数量的增加, 收集效率肯定也会大大增加(ParNew收集线程数与CPU的数量相同, 因此在CPU数量过大的环境中, 可用-XX:ParallelGCThreads=参数控制GC线程数)。

12309c1e47e7327d8faccae21fa39e30.png

ParNew的本质目的是为了缩短安全点(暂停的)的时间。

在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。

1.4.3 并行GC (Parallel Scavenge)

与ParNew类似,Parallel Scavenge也是使用复制算法,也是并行多线程收集器。 但与其他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge更关注系统吞吐量。

系统吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。

其实就是说,Parallel Scavenge关注的是宏观层面的。例如一个月这段时间内,垃圾回收的时间占比大小。而ParNew关注的微观层面的,即每次垃圾回收的时间。

停顿时间越短就越适用于用户交互的程序,良好的响应速度能提升用户的体验;而高吞吐量则适用于后台运算而不需要太多交互的任务,可以最高效率地利用CPU时间,尽快地完成程序的运算任务。

1.5 老年代垃圾回收执行机制

1.5.1 SerialOld GC

Serial Old是Serial收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法。(已过时了,了解即可)

1.5.2 ParallelOldGC

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法,吞吐量优先,主要与Parallel Scavenge配合在注重吞吐量及CPU资源敏感系统内使用;

1.5.13 CMS收集器(Concurrent Mark Sweep)

CMS(Concurrent Mark Sweep)收集器是一款具有划时代意义的收集器, 一款真正意义上的并发收集器。 虽然现在已经有了理论意义上表现更好的G1收集器,但现在主流互联网企业线上选用的仍是CMS(如Taobao、微店)。

CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器), 基于”标记-清除”算法实现, 整个GC过程分为以下4个步骤:

1. 初始标记(CMS initial mark)

2. 并发标记(CMS concurrent mark: GC Roots Tracing过程)

3. 重新标记(CMS remark)

4. 并发清除(CMS concurrent sweep: 已死对象将会就地释放, 注意:此处没有压缩)

其中1,3两个步骤(初始标记、重新标记)仍需STW. 但初始标记仅只标记一下GC Roots能直接关联到的对象, 速度很快; 而重新标记则是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录, 虽然一般比初始标记阶段稍长, 但要远小于并发标记时间.

CMS特点:

1. CMS默认启动的回收线程数=(CPU数目+3)4

当CPU数>4时, GC线程一般占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低.

2.无法处理浮动垃圾, 可能出现Promotion Failure、Concurrent Mode Failure而导致另一次FullGC的产生: 浮动垃圾是指在CMS并发清理阶段用户线程运行而产生的新垃圾. 由于在GC阶段用户线程还需运行, 因此还需要预留足够的内存空间给用户线程使用, 导致CMS不能像其他收集器那样等到老年代几乎填满了再进行收集. 因此CMS提供了-XX:CMSInitiatingOccupancyFraction参数来设置GC的触发百分比(以及-XX:+UseCMSInitiatingOccupancyOnly来启用该触发百分比), 当老年代的使用空间超过该比例后CMS就会被触发(JDK 1.6之后默认92%). 但当CMS运行期间预留的内存无法满足程序需要, 就会出现上述Promotion Failure等失败, 这时VM将启动后备预案: 临时启用Serial Old收集器来重新执行Full GC(CMS通常配合大内存使用, 一旦大内存转入串行的Serial GC, 那停顿的时间就是大家都不愿看到的了).

3.最后, 由于CMS采用”标记-清除”算法实现, 可能会产生大量内存碎片. 内存碎片过多可能会导致无法分配大对象而提前触发Full GC. 因此CMS提供了-XX:+UseCMSCompactAtFullCollection开关参数, 用于在Full GC后再执行一个碎片整理过程. 但内存整理是无法并发的, 内存碎片问题虽然没有了, 但停顿时间也因此变长了, 因此CMS还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction用于设置在执行N次不进行内存整理的Full GC后, 跟着来一次带整理的(默认为0: 每次进入Full GC时都进行碎片整理).

1.6 分区收集- G1收集器

G1(Garbage-First)是一款面向服务端应用的收集器, 主要目标用于配备多颗CPU的服务器治理大内存。

-XX:+UseG1GC 启用G1收集器.

与其他基于分代的收集器不同, G1将整个Java堆划分为多个大小相等的独立区域(Region)。 虽然还保 留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。如:

1cfd177a6907cd4837ad66c3d6bf2ab7.png

每块区域既有可能属于O区、也有可能是Y区, 因此不需要一次就对整个老年代/新生代回收。而是当线程并发寻找可回收的对象时,有些区块包含可回收的对象要比其他区块多很多。虽然在清理这些区块时G1仍然需要暂停应用线程,但可以用相对较少的时间优先回收垃圾较多的Region。这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率。

G1的新生代收集跟ParNew类似: 存活的对象被转移到一个/多个Survivor Regions. 如果存活时间达到阀值, 这部分对象就会被提升到老年代.如图:

73bd09086959a5aa44ca1e045f9b48ef.png
dc2a5fe1723ce6839c962afee267ba76.png

其特点是:

一整块堆内存被分为多个Regions.

存活对象被拷贝到新的Survivor区或老年代.

年轻代内存由一组不连续的heap区组成, 这种方法使得可以动态调整各代区域尺寸.

Young GC会有STW事件, 进行时所有应用程序线程都会被暂停.

多线程并发GC.

G1老年代GC特点如下:

并发标记阶段

1 在与应用程序并发执行的过程中会计算活跃度信息.

2 这些活跃度信息标识出那些regions最适合在STW期间回收(which regions will be best to reclaim during an evacuation pause).

3 不像CMS有清理阶段.

再次标记阶段

1 使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多.

2 空region直接被回收.

拷贝/清理阶段(Copying/Cleanup Phase)

1 年轻代与老年代同时回收.

2 老年代内存回收会基于他的活跃度信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值