垃圾回收机制

垃圾回收

概述

垃圾收集机制并不是java语言首创的.

但是又是java的招牌, java可以自动垃圾回收.

垃圾回收:

​ 回收哪些区域 频繁回收内存,较少回收方法区 栈,本地方法栈,程序计数器没有垃圾回收

什么时候回收

怎么回收

什么是垃圾

垃圾是指在运行程序中没有任何引用指向的对象,这个对象就是需要被回收的垃

圾。

为什么需要 GC?

垃圾如果不及时清理,越积越多,可能会导致内存溢出.

垃圾多了,内存碎片较多 例如数组 需要连续空间

早期是手动回收不被使用的对象 例如 C++

java语言是自动垃圾收集的.

垃圾回收机制

自动内存管理

无需开发人员手动参与内存的分配与回收,这样降低内存泄漏和 内存溢出的风险

更专心地专注 于业务开发

自动收集的担忧

​ 自动回收方便了程序员的开发,但是降低处理内存问题的能力.

​ 自动虽好,但是还是应该了解并掌握一些相关内存管理的知识.

​ 会弱化 Java 开发人员在程序出现内存溢出时定位问题和解决问题的能力。

Java 堆是垃圾收集器的工作重点

从次数上讲:

频繁收集 Young 区

较少收集 Old 区

基本不收集元空间(方法区)

内存溢出与内存泄漏

溢出: 内存不够用了

泄漏: 有些对象已经在程序不被使用了,但是垃圾回收机制并不能判定其为垃圾对象,不能将其回收掉,

​ 这样的对象越积越多, 长久也会导致内存不够用.

​ 例如: 与数据库连接完之后,需要关闭连接通道,但是没有关闭.

​ io 读写完成后没有关闭

垃圾收集算法分为两大类:

1.垃圾标记阶段算法

主要是来判定哪些对象已经不再被使用,标记为垃圾对象.

判定对象为垃圾的标准: 不被任何引用所指向的对象. Object obj = new Object();

垃圾回收阶段算法:

引用计数算法(在jvm中不被使用)

​ 如果有一个引用指向此对象,那么计数器加1. 如果没有引用指向,计数器为0, 此时就判定为垃圾.

​ 优点:方便使用,设计简洁.

​ 缺点: 增加了计数器的存储空间,计数需要消耗时间.

​ 导致一个循环引用问题. 好几个对象之间相互引用,但是没有其他引用指向他们,此时垃圾回收不能回收他们,但是也没有引用指向. 这就造成了内存泄漏

Object obj = new Object();

​ obj=null;

可达性分析算法/根搜素算法(这是java目前所使用的垃圾标记算法)

​ 解决 循环引用问题, 设计简单 ,运行高效,防止内存泄漏

思路:

​ 从一些活跃引用(GCRoots 根)开始, 如果对象被根直接或间接引用,那么此对象不是垃圾, 否则标记为垃圾对象.

哪些引用被用来当做根:

​ 虚拟机栈中引用的对象 (方法中引用的对象)

​ 本地方法栈中引用的对象

​ 静态变量所引用的对象

​ 常量引用指向的对象

被synchronized当做锁的对象

Java 虚拟机内部的引用

总结: 栈中引用的(正在使用的) 方法区,常量池中(生命周期较长的),被synchronized当做锁的对象

final(关键字) finally(代码块) finalize()(方法) 是Object类中的一个方法,在对象被最终回收之前调用,只调用一次.

八股文—>背 理解了背一遍就记住了 加入想法,上升到一个全局思想. 不理解 天天背也不理解

finalize() 方法机制

java允许对象在销毁前去调用finalize(),去处理一些逻辑. 一般不用(不建议用)

public class CanReliveObj {

	public static CanReliveObj obj;//类变量,属于 GC Root


	//此方法只能被调用一次
	 @Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("调用当前类重写的finalize()方法");
		obj = this;//当前待回收的对象在finalize()方法中与引用链上的一个对象obj建立了联系
	}


	public static void main(String[] args) {
		try {
			obj = new CanReliveObj();
			// 对象第一次成功拯救自己
			obj = null;
			System.gc();//调用垃圾回收器
			System.out.println("第1次 gc");
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}
			System.out.println("第2次 gc");
			// 下面这段代码与上面的完全相同,但是这次自救却失败了
			obj = null;
			System.gc();
			// 因为Finalizer线程优先级很低,暂停2秒,以等待它
			Thread.sleep(2000);
			if (obj == null) {
				System.out.println("obj is dead");
			} else {
				System.out.println("obj is still alive");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}


}

不要自己显示的去调用finalize()方法,在里面写代码一定要慎重

在 finalize()时可能会导致对象复活。

finalize()由垃圾回收器调用,没有固定的时间.

一个糟糕的 finalize()会严重影响 GC 的性能。比如 finalize 是个死循环。

对象状态:

**可触及的:**从根节点开始,可以到达这个对象 。 (没有被标记为垃圾)

**可复活的:**对象的所有引用都被释放,但是对象有可能在 finalize()中复活。 确定为垃圾了,但没有调用finalize()方法.

**不可触及的:**对象的 finalize()被调用,并且没有复活,那么就会进入不可触及 状态。不可触及的对象不可能被复活,因为 finalize()只会被调用一次.

2.垃圾回收阶段算法

1.标记-清除算法

​ 分为两个阶段:

​ 标记: 标记出从根可达的对象,标记的是被引用的对象.

​ 清除: 此清除并非直接将垃圾对象清除掉.

​ 而是将垃圾对象的地址维护到一个空闲列表中,

​ 之后如果有新的对象产生,判断空闲列表中的对象空间能否存放得下新的对象,如果能放得下,那么就覆盖垃 圾对象.

优点: 简单,容易理解

缺点: 效率低, 会产生STW(在回收时,停止整个应用程序), 会产生内存碎片.

2.复制算法

将内存分为大小相等的两块,每次只使用其中的一块儿区域即可.

当回收时,将不是垃圾的对象,复制到另一块内存中,排放整齐.

然后将原来的内存块清空.

减少内存碎片.

优点: 运行高效,减少内存碎片

缺点: 用到两倍的内存空间 ,对于G1垃圾回收器,将每个区域又可以拆分成更多的小区域,需要维护各区之间的关系.

在新生代中的幸存者0和幸存者1这两个区域使用复制算法.

3.标记压缩算法

背景:

​ 复制算法需要移动对象位置,移动的数量如果多的情况下,效率低. 对于年轻代来讲还是不错的.

对于老年代,大量的对象是存活的. 如果需要移动就比较麻烦效率低.

实现:

将存活对象标记出来,重新在本内存空间中排放位置.

清除其他空间的垃圾对象.

标记-清除 和 标记-压缩对比

标记清除是不移动对象, 不会把垃圾对象清除掉(维护在一个空闲列表中)

标记-压缩是要移动对象的. 要清除掉垃圾对象.

优点: 不会像标记-清除算法那样会产生内存碎片

​ 不会像复制算法那样需要两块内存空间

缺点:

效率相对低, 对象位置移动后需要重新设置对象地址, 也会有STW

分代/分区收集

由于对象的生命周期长短不同,将不同的对象存储在不同的区域.

针对不同的区域进行分区收集,提高收集效率.

垃圾回收中的相关概念

System.gc() 的理解

调用System.gc()方法,会触发FULL GC(整堆收集),

但是不一定调用后会立刻生效,因为垃圾回收是自动的.

一般情况下,不要在项目中显示的去调用.

Stop the World

Stop the World -->STW 在垃圾回收时,会导致整个应用程序停止.

在标记垃圾对象时,需要以某个时间节点上内存中的情况进行分析(拍照 快照)

因为不进行停顿的话,内存中的对象不停的变化,导致分析结果不准确.

停顿是不可避免的,优秀的垃圾回收器尽可能减少停顿的时间.

对象引用

Object obj = new Object();

就是将对象分等级: 强引用(有引用指向的对象) 软引用 弱引用 虚引用(都是垃圾了)

强引用: Object obj = new Object(); obj引用创建的对象 那么此对象就是被强引用的.

​ 这种情况下,即使内存不够用了,报内存溢出,也不会回收.

软引用: 当内存足够使用时,先不回收这类对象,当虚拟机内存不够用时,要回收此类对象.

弱引用: 此类对象只能生存到下次垃圾回收时, 只要发生垃圾回收,就会回收此类对象.

虚引用: 发现即回收.

内存溢出与内存泄漏

垃圾回收器

比较底层,了解垃圾回收器的一些种类及实现.

垃圾回收器(具体实现垃圾回收的收集器名称)

垃圾回收器分类

按线程数分,可以分为串行垃圾回收器和并行垃圾回收器

按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器

按工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器

垃圾回收器的性能指标

吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运

行时间+内存回收的时间)

垃圾收集开销:垃圾收集所用时间与总运行时间的比例。

暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。

收集频率:相对于应用程序的执行,收集操作发生的频率。

内存占用:Java 堆区所占的内存大小。

快速:一个对象从诞生到被回收所经历的时间。

常见的垃圾收集器

CMS 回收器(低延迟)

初始标记:

Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直

接关联的对象进行标记。

并发标记:使用多条标记线程,与用户线程并发执行。此过程进行可达性分析, 标记出所有废弃对象。速度很慢。

重新标记:Stop The World,使用多条标记线程并发执行,将刚才并发标记过 程中新出现的废弃对象标记出来。

并发清除:只使用一条 GC 线程,与用户线程并发执行,清除刚才标记的对象。 这个过程非常耗时。

并发标记与并发清除过程耗时最长,且可以与用户线程一起工作,因此,总体上 说,CMS 收集器的内存回收过程是与用户线程一起并发执行的

在这里插入图片描述

CMS 的优点:

并发收集

低延迟

CMS 的弊端:

  1. 会产生内存碎片,导致并发清除后,用户线程可用的空间不足。在无法分配

大对象的青况下,不得不提前触发 Ful1 GC.

2.CMS 收集器对 CPU 资源非常敏感。在并发阶段,它虽然不会导致用户停顿,

但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低

G1(Garbage First)回收器(区域划分代式)

因为 G1 是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)

(物理上不连续的)。使用不同的 Region 来表示 Eden、幸存者 0 区,幸存者

1 区,老年代等。

G1 GC 有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各

个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时

间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收

价值最大的 Region.

由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给 G1 一个名字:垃圾优先(Garbage First)。

G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备

多核 CPU 及大容量内存的机器,以极高概率满足 Gc 停顿时间的同时,还兼具

高吞吐量的性能特征。

G1 收集器可以 “ 建立可预测的停顿时间模型 ”,它维护了一个列表用

于记录每个 Region 回收的价值大小(回收后获得的空间大小以及回收所需时

间的经验值),这样可以保证 G1 收集器在有限的时间内可以获得最大的回收

效率。

如下图所示,G1 收集器收集器收集过程有初始标记、并发标记、最终标记、

筛选回收,和 CMS 收集器前几步的收集过程很相似:

在这里插入图片描述

① 初始标记:标记出 GC Roots 直接关联的对象,这个阶段速度较快,需

要停止用户线程,单线程执行。

② 并发标记:从 GC Root 开始对堆中的对象进行可达新分析,找出存活

对象,这个阶段耗时较长,但可以和用户线程并发执行。

③ 最终标记:修正在并发标记阶段引用户程序执行而产生变动的标记记录。

④ 筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划

适用场景:要求尽可能可控 GC 停顿时间;内存占用较大的应用。可以用

-XX:+UseG1GC 使用 G1 收集器,jdk9 默认使用 G1 收集器。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值