Jvm常识之垃圾回收器

2019-07-10  本文已影响0人  cefa6a30d1c3

垃圾回收算法理论

复制算法

复制算法将可用的内存容量划分成大小相等的两块,每次只使用其中的一块;当这一块内存用完,就会将还存活的对象放在另一块区域上,然后再把已使用的内存空间一次清理掉,这样每次清理垃圾的时候都是对整个半区进行垃圾回收,内存分配的时候也不用考虑内存碎片的问题了,这样对于内存的回收就更加简单高效。

1540372816097.png

但是这种算法也有缺点:

标记-清除算法

标记-清除算法是最基础的垃圾回收算法;

算法的分为两个阶段:

1:标记阶段

2:清除阶段

首先标记所有需要回收的对象,在标记完成之后统一回收所有被标记的对象;


1540372926729.png

标记-清除算法有两个不足之处:

1:一个是效率问题,标记和清除两个过程的效率都不高

2:空间问题:标记清除后会产生大量不连续的内存碎片(空间碎片太多可能会导致后续的程序运行过程中需要分配较大的对象时,无法找到足够的连续内存,这样就导致不得不提前出发垃圾收集动作);

标记-整理算法

标记-整理算法和标记-清除算法很相似,但是标记整理算法并不是直接对可回收对象进行清理,而是让所有存活的对象都像一端移动,然后直接清理掉端边界以外的内存;

1540372983639.png

JDK1.7 - 垃圾回收

1540373494951.png

内存阶段划分

年轻代

年轻代是由Eden space和两个suvisor组成的,在初始阶段,新创建的对象会分配给Eden区(如果创建的对象非常大,那么对象会直接进去老年代),两个Suvisor区是空的。

随着对象往Eden区进行填充,Eden区满了的时候,就会触young GC------ Minor GC

在这阶段会使用垃圾回收的算法---复制算法(复制算法会将存活的对象复制到from suvisor区域,然后已经无用的对象被回收)

1540373632804.png

随着minorGC的不断进行,会反复重复上面的过程,只要经过复制过程,年龄就会加1;

当对象的年龄不断的增长,达到一个默认值“15”( -XX:MaxTenuringThreshold)的时候,对象就会进入老年代了。(在发生minor GC的时候。JVM都会去检查每次晋升到老年代的对象大小是否已经大于老年代剩余的空间,如果大于那么就会出现FULL GC)。

年老代

随着年轻代的GC重复操作,只要年龄达到了哪个触发点,就会把年轻代的对象复制到老年代里面;那么随着时间的推移,老年代的对象会越来越多,最终老年代的空间区域也会不够,就会出现老年的GC-------Major GC(标记清除或者标记-整理)

(年老带对象存满时,会触发一次FullGC,对堆内存空间整体清除)

持久代(方法区)

持久代=方法区

主要存放Class和Meta的信息,Class在被加载的时候被放入永久代。 它和存放对象的堆区域不同,GC(Garbage Collection)不会在主程序运行期对永久代进行清理,所以如果你的应用程序会加载很多Class的话,就很可能出现PermGen space错误。

方法区物理上存在于堆里,而且是在堆的持久代里面;但在逻辑上,方法区和堆是独立的。
一般说堆的持久代就是说方法区,因为一旦JVM把方法区(类信息,常量池,静态字段,方法)加载进内存以后,这些内存一般是不会被回收的了。

JDK1.8 +垃圾回收

JDK8 自带先进的使用G1回收器。

Oracle官方已经在JDK9中将G1变成默认的垃圾收集器

G1是将整个堆空间划分成大小想等的小块(每一块成为region),每一块的内存是连续的。和分代收集算法一样,G1中每个快也会充当Eden、Suvisor、Old三种角色,但是他们不是固定的,这样使得内存的使用更加灵活;

1540374259289.png

JVM垃圾回收器

Serial收集器

单线程收集器,“单线程”的意义不仅仅说明它只会使用一个CPU或一个收集线程去完成垃圾收集工作;

更重要的是它在垃圾收集的时候,必须暂停其他工作线程,直到垃圾收集完毕;-----Stop-the-world

1540374522368.png

Serial收集器也并不是只有缺点;Serial收集器由于简单并且高效;

对于单CPU环境来说,由于Serial收集器没有线程间的交互,专心做垃圾收集自然可以做获得最高的垃圾收集效率

使用方式:-XX:+UseSerialGC


ParNew 收集器

ParNew收集器是Serial收集器的多线程版本;

除了使用多线程收集以外,其余行为和Serial收集器是一样的(收集算法、stopTheWorld)


1540374624282.png

ParNew收集器在单CPU服务器上的垃圾收集效率绝对不会比Serial收集器高;

但是在多CPU服务器上,效果会明显比Serial好

使用方式:-XX:+UseParNewGC


Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代的收集器,并且使用复制算法,而且是一个并行的多线程收集器

Parallel Scavenge收集器的关注的点和其他收集器是不一样的;

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,

使用方式:-XX:+UseParallelGC


Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是被Client模式下的虚拟机使用。


1540375056674.png

使用方式:-XX:+UseSerialGC


Parallel Old收集器

Parallel Old是parallel Scavenge的多线程版本,使用的是标记-整理算法;

Parallel Old收集器的工作过程:


1540375205440.png

使用方式: -XX:+UseParallelOldGC


CMS 收集器

CMS(concurrent mark sweep)是以获取最短垃圾收集停顿时间为目标的收集器;

目前很大一部分的java应用几种在互联网的B/S系统服务器上,这类应用尤其注重服务器的响应速度,希望系统停顿时间最短,给用户带来良好的体验;

CMS收集器使用的算法是标记-清除算法实现的;

整个过程分4个步骤:

1、 初始标记

2、 并发标记

3、 重新标记

4、 并发清除

其中初始标记和重新标记都需要stopTheWorld

1540375339596.png

CMS垃圾收集器缺点:

1:CMS收集器对CPU资源特别的敏感;CMS在并发阶段,虽然不会导致用户线程停顿,但是会因为占用一部分线程而导致应用程序变慢,总吞吐量变低;

2:使用标记-清除算法,会产生内存碎片(配合-XX:+UseCMSCompactAtFullCollection使用)

使用方式:-XX:+UseConcMarkSweepGC

6.7 G1收集器

1540375564616.png

G1收集器是最新的垃圾收集器,能效最好的收集器;

考虑到之前的垃圾收集器的优缺点,希望能够有这样一款收集器能够做到:

内存划分

G1是将整个堆空间划分成大小想等的小块(每一块成为region),每一块的内存是连续的。和分代收集算法一样,G1中每个快也会充当Eden、Suvisor、Old三种角色,但是他们不是固定的,这样使得内存的使用更加灵活;

原来分代存储每个代的空间都是固定的比例,现在很灵活,不需要固定下。

1540375594708.png
在G1中有个特殊的区域叫做:Humongous区域。如果一个对象的空间超过了分区容量的50%以上,G1就认为这是一个巨型对象;默认巨型对象是需要存储在老年代中的,但是如果这个巨型对象只是短期存在,那么会对垃圾收集器造成负面影响;为了解决这个问题,G1专门划分了一个区域(Humongous)用来存储大对象;

注意:如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

注意:一个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定。


GC模式

G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的

Full GC

如果对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免full gc.

部分参数

-XX:+UseG1GC 使用 G1 (Garbage First) 垃圾收集器
XX:MaxGCPauseMillis=n 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标.
XX:InitiatingHeapOccupancyPercent=n 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45.
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8
-XX:MaxTenuringThreshold=n 提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n 设置垃圾收集器在并行阶段使用的线程数
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量
-XX:G1HeapRegionSize=n 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.

持久代不见了

​ 随着JDK8的到来,JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。

​ 类的元数据信息转移到Metaspace的原因是PermGen很难调整。PermGen中类的元数据信息在每次FullGC的时候可能会被收集,但成绩很难令人满意。而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class的总数,常量池的大小,方法的大小等。

​ 此外,在HotSpot中的每个垃圾收集器需要专门的代码来处理存储在PermGen中的类的元数据信息。从PermGen分离类的元数据信息到Metaspace,由于Metaspace的分配具有和Java Heap相同的地址空间,因此Metaspace和Java Heap可以无缝的管理,而且简化了FullGC的过程,以至将来可以并行的对元数据信息进行垃圾收集,而没有GC暂停。

补充一些Jvm参数

- -Xms:JVM初始最小堆内存
- -Xmx:JVM允许最大堆内存
- -XX:PermSize  JVM初始非堆内存
- -XX:MaxPermSize  JVM允许最大的非堆内存
- -XX:+UseConcMarkSweepGC:年老代激活CMS收集器(标记算法),可以尽量减少fullGC
- -XX:+UseParNewGC :设置年轻代为多线程并行收集
- -XX:+UseCMSCompactAtFullCollection:在FULL GC的时候,对年老代的压缩(CMS的时候,会导致内存碎片,使内存空间不连续,可能会影响性能,但是可以消除碎片)
- -XX:CMSInitiatingOccupancyFraction=85:当年老代空间被占用85%的时候触发CMS垃圾收集
上一篇下一篇

猜你喜欢

热点阅读