JVM学习(四)-垃圾回收器和内存分配

1. 判断对象存活

回收内存首先需要判断,那些内存需要回收。即需要判断那些对象还存活着,则这些是不需要被回收的。

(1) 引用计数法

原理:对象中添加一个引用计数器。被引用则累计。则计数器中数值大于0,则代表仍然被引用,不能被回收。

缺点:不能解决循环引用的情况。

(2) 可达性分析法

原理:从一些称为GCRoots的对象结点开始,向下开始搜索,根据是否能到达来判断对象的存活。其中,从GCRoots对象到目标对象之间的路径称为引用链。

 

GCRoots的对象包含以下几种:JAVA虚拟栈中的局部变量表中的引用对象、本地方法栈中引用的对象、方法区中静态属性引用的对象、方法区中,常量引用的对象

2. 垃圾回收算法

(1) 标记-清除法

原理:首先先标记需要回收的对象,再标记完成后统一回收所有标记的对象。

缺点:效率【标记和回收的效率都不高】、空间【只是将需要回收的对象回收,没有将空间系统的整理,所以,会有很多的内存碎片,使用起来不好使用】

(2) 标记-复制法

原理:将内存分为两部分S1S2,每次使用其中的一部分,比如S1。等S1空间满了的时候,将其中未被标记出来的【存活的对象】复制到另外一个内存空间S2中,回收整个S1

缺点:内存使用只有原来的一半。如果存活率较高的情况下,这种算法效率会降低。

(3) 标记-整理法

原理:首先标记需要被删除的对象。然后将所有存活的对象向一侧移动。完成后清理掉端边以外的内存。

(4) 分代收集法

根据内存存活周期的不同,划分成不同的几块。JAVA堆一般分为新生代和老年代。根据各自年代的特点,选择适合的收集算法。比如:新生代存活率低,因此,新生代选择复制算法。老年代存活率较高,没有额外空间对其进行担保,必行采用“标记-清除”算法或者“标记-清理”算法进行回收

① 新生代的回收算法

1) 新生代内存按照8:1:1的比例分为一个eden区和两个survivor (survivor0,survivor1)区。一个Eden区,两个 Survivor(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。

2) survivor1区不足以存放 edensurvivor0的存活对象时,就将存活对象直接存放到老年代。

3) 若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。

② 老年代的回收算大

1) 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

2) 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GCFull GCFull GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。

③ 永久代的回收算法

3. 垃圾回收器

Hotspot 所包含的垃圾回收器。

 

 

 

垃圾回收器从线程运行情况分类三种

① 串行回收:serial回收器,单线程回收。全程stw(Stop The World)

② 并行回收:名称以parallel开头的回收器【parallel scavenge回收器、parallel old 回收器】,多线程回收。全程(Stop The World)

③ 并发回收:CMSG1,多线程分阶段回收。只有某阶段会(Stop The World)

 

(1) Serial收集器(复制算法)

新生代单线程收集器,标记和清理都是单线程,优点是简单高效。

(2) Serial Old收集器(标记-整理算法)

老年代单线程收集器Serial收集器的老年代版本。

(3) ParNew收集器(停止-复制算法)

新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现

(4) Parallel Scavenge收集器(停止-复制算法)

并行收集器,追求高吞吐量,高效利用CPU

(5) Parallel Old收集器(停止-复制算法)

Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。

(6) CMS(Concurrent Mark Sweep)收集器(标记-清理算法)

特点

A. 只会回收老年代和永久代,不会回收年轻代。

B. CMS是一种预处理垃圾回收器,它不能等到old内存用尽时回收。需要在内存用尽前完成回收。否则会导致并发回收失败。所以,CMS垃圾回收器在开始工作有一个触发的阈值,默认为老年代或者永久代的92%

CMS基于“标记-清除”算法实现的。是最常用的垃圾回收器。CMS垃圾回收器的工作原理有7个步骤

1.初始化标记,会导致stw

2.并发标记,与用户线程同时运行。

3.预清理-与用户线程同时运行。

4.可被终止的预清理,与用户线程同时运行

5.重新标记,会导致swt

6.并发清除,与用户线程同时运行。

7.并发重置状态等待下一次CMS触发,与用户线程同时运行。

CMS运行流程如下:

 

 

4. GC的种类和触发

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GCFull GC

(1) Scavenge GC

一般情况,在创建新对象的时候,对Eden申请空间失败的时候,会触发Scavenge GC。清除Eden中非存活对象,将存活对象移动到S1中。然后整理两个S区。这种GC对老年代不影响。对Eden GC的频率比较高,因此,需要效率比较高的算法【复制算法】。

(2) Full GC

对整个堆进行整理。包含新生代,老年代。所以比Scavenge GC消耗时间长。所以,需要尽可能少的较少FULL GC的次数。

触发场景:老年代被写满(Tenured)、持久代被写满(Perm)、System.GC被显示调用。

5. 内存分配和回收策略

对象分配的时候,需要选择一片未被使用的空间。同时两个线程需要创建对象。因此,可能产生并发冲突问题。解决的方案有两个。其一:分配空间的时候,保证同一时刻只有一个线程在申请空间。其二:每个线程在堆中获得一块区域。用来创建线程自己的对象。Thread Local Allocation Buffer简称TLAB

(1) 对象优先在Eden中分配

(2) 大对象直接进入老年代

设置参数,申请空间大于设定的阈值,则直接创建在老年代中。

(3) 长期存活的对象进入老年代

根据设置晋升老年代的年龄阈值,对象超过设置的阈值,则移动到老年代。

(4) 动态对象年龄判断

根据比例选择年龄阈值。相同年龄的对象大小总和大于等于整体S的一半,则当前年龄为阈值。大于或者等于这个年龄的对象可以移动到老年代,而不需要和年龄阈值比较。

(5) 空间分配担保

新生代无法分配内存的时候,把新生代对象转移到老年代中,然后把新对象放入腾空的新生代中。

如果老年代不能做担保【有连续的空间且大于S区中所有对象的总和--S可以全部移动过去】担保失败会引起Full GC。需要尽量避免频发Full GC。所以,需要了解空间分配担保。

(一)垃圾回收器和内存分配

posted @ 2020-11-09 11:47  IT迷途小书童  阅读(83)  评论(0编辑  收藏  举报