JVM之垃圾回收

目录

1、什么是垃圾回收

2、如何确定垃圾

3、强、软、弱、虚四种引用

4、常见的垃圾回收算法

5、分代垃圾回收

6、串行、并行、并发、G1垃圾收集器


1、什么是垃圾回收

程序的运行必然要申请内存资源,无效的对象资源如果不及时清理就会一直占用内存资源,最终导致内存溢出,所以对内存资源的关联非常重要。

为了让开发者更多关注代码的实现,而不用过多的考虑内存释放的问题,所以,在Java中有了自动垃圾回收机制,也就是我们熟悉的GC。

有了垃圾回收机制后,程序员只需要关注内存的申请即可,而内存的释放由系统自动识别完成。

2、如何确定垃圾

一般来说,没有被引用的对象就是垃圾。

怎样判断对象是否可以被回收,有两种算法:

1、引用计数法(已被淘汰)

原理:假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败时,对象A的引用计数器就-1,如果对象A的计数器的值为0,就说明对象A没有引用了, 可以被回收。

目前主流的java虚拟机都摒弃掉了这种算法,最主要的原因是它很难解决对象之间相互循环引用的问题。尽管该算法执行效率很高。

2、可达性分析算法

通过判断对象的引用链是否可达来决定对象是否可以被回收。

扫描堆中的对象,看是否能够沿着 GC Root 对象 为起点的引用链找到该对象,找不到,表示可以
回收

可以作为GC Root的对象有:

1、虚拟机栈中引用的对象(栈帧中的本地变量表)

2、方法区中的常量引用对象

3、方法区中的静态属性引用对象

4、本地方法栈中JNI(Native方法)的引用对象

5、活跃线程中的引用对象

3、强、软、弱、虚四种引用

1. 强引用
只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收


2. 软引用(SoftReference)
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用
对象
可以配合引用队列来释放软引用自身


3. 弱引用(WeakReference)
仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
可以配合引用队列来释放弱引用自身


4. 虚引用(PhantomReference)
必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,
由 Reference Handler 线程调用虚引用相关方法释放直接内存


5. 终结器引用(FinalReference)
无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象
暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize
方法,第二次 GC 时才能回收被引用对象

4、常见的垃圾回收算法

1、Mark_Sweep(标记清除算法)

1、思想:标记清除算法分为两个阶段,标记阶段和清除阶段。标记阶段任务是标记出所有需要回收的对象,清除阶段就是清除被标记对象的空间。
2、优缺点:实现简单,容易产生内存碎片。

2、Copying(复制清除算法)

1、思想:将可用内存划分为大小相等的两块,每次只使用其中的一块。当进行垃圾回收的时候了,把其中存活对象全部复制到另外一块中,然后把已使用的内存空间一次清空掉。
2、优缺点:不容易产生内存碎片;可用内存空间少;存活对象多的话,效率低下。

3、Mark-Compact(标记-整理算法)

1、思想:先标记存活对象,然后把存活对象向一边移动,然后清理掉端边界以外的内存。
2、优缺点:不容易产生内存碎片;内存利用率高;存活对象多并且分散的时候,移动次数多,效率低下。

5、分代垃圾回收

前面介绍了多种回收算法,每一种算法都有自己的优点也有缺点,谁都不能替代谁,所以根据垃圾回收对象的特点进行选择,才是明智的选择。
分代算法其实就是这样的,根据回收对象的特点进行选择,在jvm中,年轻代适合使用复制算法,老年代适合使用标记清除或标记压缩算法。

1、对象首先分配在Eden区域。在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor 区“To”是空的。
2、新生代空间不足时,触发 Minor GC,Eden区中存活的对象会使用copy(复制算法)复制到Survivor To区,而Survivor From区中仍存活的对象会根据它们的年龄值决定去向。
年龄达到一定阈值(可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到老年代中,没有达到阈值的对象会被复制到Survivor To区。
3、经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。
同时,存活的对象年龄会加1。
4、Minor GC会引发Stop The World,暂停其他的用户线程,等垃圾回收结束,用户线程才恢复运行。
5、经过多次Minor GC后,当对象寿命超过阈值时,会晋升到老年代,最大寿命是15(4bit),该值存在对象头中(header)。
6、当老年代空间不足时,会先尝试触发Minor GC,如果之后空间仍不足,那么触发Full GC,STW的时间更长。

Oracle 官方建议:Oracle recommends that you keep the size for the young generation greater than 25% and less than 50% of the overall heap size.

6、串行、并行、并发、G1垃圾收集器

1、串行收集器

串行垃圾收集器,是指使用单线程进行垃圾回收,垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停,等待垃圾回收的完成。
这种现象称之为 STW(Stop-The-World)。
对于交互性较强的应用而言,这种垃圾收集器是不能够接受的。一般在Javaweb应用中是不会采用该收集器的。

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;

-XX:+UseSerialGC 指定年轻代和老年代都使用串行垃圾收集器 -XX:+PrintGCDetails 打印垃圾回收的详细信息

2、并行收集器

并行垃圾收集器在串行垃圾收集器的基础之上做了改进,将单线程改为了多线程进行垃圾回收,这样可以缩短垃圾回收的时间。
并行垃圾收集器在收集的过程中也会暂停应用程序,这个和串行垃圾回收器是一样的,只是并行执行,速度更快些,暂停的时间更短一些。(多核CPU机器)

  • ParNew收集器 (复制算法): 新生代并行收集集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

通过-XX:+UseParNewGC参数设置年轻代使用ParNew回收器,老年代使用的依然是串行收集器。

  • ParallelGC收集器工作机制和ParNewGC收集器一样,只是在此基础之上,新增了两个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。

Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
Parallel Old收集器 (标记-整理算法):老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

JDK8默认使用PS+PO垃圾回收器
-XX:+UseParallelGC 年轻代使用ParallelGC垃圾回收器,老年代使用串行回收器。
-XX:+UseParallelOldGC 年轻代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。
-XX:MaxGCPauseMillis 设置最大的垃圾收集时的停顿时间,单位为毫秒,需要注意的是,ParallelGC为了达到设置的停顿时间,可能会调整堆大小或其他 的参数,如果堆的大小设置的较小,就会导致GC工作变得很频繁,反而可能会影响到性能。 该参数使用需谨慎。
-XX:GCTimeRatio 设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)。它的值为0~100之间的数字,默认值为99,也就是垃圾回收时间不能超过1%
-XX:UseAdaptiveSizePolicy 自适应GC模式,垃圾回收器将自动调整年轻代、老年代等参数,达到吞吐量、 堆大小、停顿时间之间的平衡。一般用于,手动调整参数比较困难的场景,让收集器自动进行调整。

3、CMS(并发)垃圾收集器

CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器,该回收器是针对老年代垃圾回收的,通过参数-XX:+UseConcMarkSweepGC进行设置。

CMS垃圾回收器的执行过程如下:

  1. 初始化标记(CMS-initial-mark) ,标记root,会导致stw;
  2. 并发标记(CMS-concurrent-mark),与用户线程同时运行;
  3. 预清理(CMS-concurrent-preclean),与用户线程同时运行;
  4. 重新标记(CMS-remark) ,会导致stw;
  5. 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
  6. 调整堆大小,设置CMS在清理之后进行内存压缩,目的是清理内存中的碎片;
  7. 并发重置状态,等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;

4、G1垃圾收集器

G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方计划在jdk9中将 G1变成默认的垃圾收集器,以替代CMS。
G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优:
1. 第一步,开启G1垃圾收集器
2. 第二步,设置堆的最大内存
3. 第三步,设置最大的停顿时间

G1中提供了三种模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不同的条件下被触发。

原理:

G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。
这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

特点
同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms;
超大堆内存,会将堆划分为多个大小相等的 Region,Region的大小是1MB~32MB;
整体上是 标记+整理 算法,两个区域之间是复制算法。

G1 划分的区域中,年轻代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者 Survivor 空间, G1 收集器通过将对象从一个区域复制到另外一个区 域,完成了清理工作。
这就意味着,在正常的处理过程中, G1 完成了堆的压缩(至少是部分堆的压缩),这样也就不会有 cms 内存碎片问题的存在了。
 
G1 中,有一种特殊的区域,叫 Humongous 区域。
  • 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。
  • 这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
  • 为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果 一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续H区,有时候不得不启动Full GC。

1)Young GC

Young GC 主要是对 Eden 区进行 GC ,它在 Eden 空间耗尽时会被触发。
  • Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。
  • Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。
  • 最终Eden空间的数据为空,GC停止工作,应用线程继续执行。

Remembered Set(已记忆集合)

在GC年轻代的对象时,我们如何找到年轻代中对象的根对象呢?
根对象可能是在年轻代中,也可以在老年代中,那么老年代中的所有对象都是根么?
如果全量扫描老年代,那么这样扫描下来会耗费大量的时间。
于是,G1引进了RSet的概念。它的全称是Remembered Set,其作用是跟踪指向某个堆内的对象引用。

每个Region初始化时,会初始化一个RSet,该集合用来记录并跟踪其它Region指向该Region中对象的引用,每个Region默认按照512Kb划分成多个Card,所以RSet需要记录的东西应该是 xx Region的 xx Card。

2)Mixed GC

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个YoungRegion,还会回收一部分的Old Region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC 并不是 Full GC。

MixedGC什么时候触发? 由参数 -XX:InitiatingHeapOccupancyPercent=n 决定。默认:45%,该参数的意思是:当老年代大小占整个堆大小百分比达到该阀值时触发。
它的GC步骤分2步:
1. 全局并发标记(global concurrent marking)
2. 拷贝存活对象(evacuation)

1、全局并发标记(global concurrent marking)

全局并发标记,执行过程分为五个步骤:

  1. 初始标记(initial mark,STW):标记从根节点直接可达的对象,这个阶段会执行一次年轻代GC,会产生全局停顿。
  2. 根区域扫描(root region scan):G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。
  3. 并发标记(Concurrent Marking):G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断。
  4. 重新标记(Remark,STW):该阶段是 STW 回收,因为程序在运行,针对上一次的标记进行修正。
  5. 清除垃圾(Cleanup,STW):清点和重置标记状态,该阶段会STW,这个阶段并不会实际上去做垃圾的收集,等待evacuation阶段来回收。

 2、拷贝存活对象(evacuation)

Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。

3)G1收集器相关参数

-XX:+UseG1GC    使用 G1 垃圾收集器。
-XX:MaxGCPauseMillis    设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是 200 毫秒。
-XX:G1HeapRegionSize=n    设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域。默认是堆内存的1/2000。
-XX:ParallelGCThreads=n    设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。
-XX:ConcGCThreads=n    设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads)的 1/4 左右。
-XX:InitiatingHeapOccupancyPercent=n    设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。

4)对于G1垃圾收集器优化建议

1、年轻代大小

  • 避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。
  • 固定年轻代的大小会覆盖暂停时间目标。

2、暂停时间目标不要太过严苛

  • G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。
  • 评估 G1 GC 的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值