Java的JVM垃圾回收机制GC概述


1、什么是GC?

JVM垃圾收集(Java garbage collection)。

GC采用的分带收集算法:

  • 次数上频繁收集Young区。
  • 次数上较少收集Old区。
  • 基本不会动Perm区。

2、GC算法的总体概述

在这里插入图片描述
JVM在进行gc时,并非每次都会对上面的三个内存区域一起回收,大部分时候回收的都只是新生代

GC按照回收的区域分了两种类型:

  • 普通GC(又称之为minor gc):只针对新生代区域的gc。
  • 全局GC(major gc or full gc):针对老年代的gc,偶尔伴随着新生代的gc以及对永久代的gc。

3、JVM所处的位置

JVM运行在操作系统OS之上,与硬件并没有直接的交互。
在这里插入图片描述

4、JVM整体结构

  • HotSpot VM是当前市面上高性能Java虚拟机的代表作之一。
  • 采用解释器 即时编译并存的架构,解释器也就是虚拟机处理字段码的CPU;
  • JVM运行整体逻辑框图如下:
    在这里插入图片描述
    其中,执行引擎包含三部分:解释器、即时编译器器、垃圾回收GC

5、JVM架构模型

Java编译器输入的指令流基本上都是按照基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构,具体差异如下:

基于栈指令集架构:

  • 设计实现简单,适用于资源受限的系统;
  • 避开了寄存器的分配难题,使用零地址指令方式分配;
  • 指令流中的指令大部分都是零地址指令,执行过程依赖于操作栈。指令集更小,编译器更容易实现;
  • 不需要硬件级支持,可移植性好,更好实现跨平台

基于寄存器指令集架构

  • 典型的应用是X86的二进制指令集,例如传统PC以及Android的Davlik 虚拟机
  • 指令集架构则完全依赖于硬件,可移植性差。
  • 性能优秀和执行高效。
  • 花费了更少的指令去完成 一项操作 。
  • 大部分情况下,基于寄存器架构的指令集往往都是以一地址指令、二地址指令三地址指令为主,而基于栈结构的指令集却往往以零地址指令为主要。

6、Java垃圾回收机制优缺点

优点

  • 自动内存管理,无序开发人员手动参与内存的分配和回收,降低内存泄露或者溢出的风险。
  • 自动内存管理机制,让开发人员着重业务逻辑的实现。
  • oracle官网关于GC机制的介绍:GC机制概述。

7、GC主要关注的区域

GC主要关注方法区的垃圾收集。
在这里插入图片描述
垃圾收集可以对年轻代回收,也可以对老年代回收,甚至是全栈和方法区 的回收。

垃圾回收算法:标记阶段,引用计数

堆里存放的几乎所有Java对象,在GC执行垃圾回收之前,首先休要区分内存哪些是存活的对象,哪些是已经死亡的对象。

一般判定对象存活的方式有两种:引用计数可达性分析算法

引用计数算法(reference counting)对每一个对象保存一个整型的引用计数器属性,记录对象被引用的情况。

  • 对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器+1;当引用失效时,引用计数器-1;
  • 若对象A的引用计数器数值0,则表示对象A不可能被使用,可以被GC。
  • 优点:实现简单,垃圾对象便于辨识,判定效率高,回收没有延迟。
  • 缺点:需要单独的字段存储计数器,增加了存储空间的开销。

每次赋值都需要更新计数器,伴随着加法和减法操作,增加了时间开销。
引用计数器有一个严重的问题,无法处理循环引用的情况,这是致命缺陷。

循环引用

若p的指针断开的时候,内部的引用形成了一个循环,这就是循环引用。从而造成了内存的泄露。

在这里插入图片描述
给出测试代码:

public class RefCountGC {
	// 这个成员属性的唯一作用就是占用内存空间
	private byte[] bigSize = new byte[5 * 1025 * 1024];
	// 引用
	Object reference = null;
	
	public static void main(String[] args) {
		RefCountGC obj1 = new RefCountGC();
		RefCountGC obj2 = new RefCountGC();
		obj1.reference = obj2;
		obj2.reference = obj1;
		obj1 = null;
		obj2 = null;
		// 显式的执行GC行为,判定obj1和obj2是否被gc了
		System.gc();
	}
}

若使用了引用计数算法,则这两个对象都无法被GC,但是测试会发现均被回收了,则说明Java使用的并不是上述提及的算法。


标记阶段:可达性分析算法

可达性分析算法:可以称之为根搜索算法、追踪性能垃圾收集

相比较于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是可以有效解决循环引用的问题,防止内存泄露的发生。

实现基本思路:

  • 可达性分析算法是以根对象集合(GCroot)为起始点,按照从上至下方式搜索被根对象集合所连接的目标对象是否可达。
  • 使用了可达性分析算法后,内存存活对象都会被根对象直接或者间接连接着,搜索所走过的路径称之为引用链(Reference Chain)
  • 若目标对象没有任何引用链相连,则不可达,意味着该对象已经死亡,可以被GC。
  • 在可达性分析算法中,只有能够被根对象集合直接或者简介链接的对象才是存活对象
    在这里插入图片描述

GC root可以是哪些?

  • 虚拟机栈中引用的对象,例如各个线程调用的方法中使用到的参数、局部变量等等。
  • 本地方法的栈内JNI(也就是通常说的本地方法 )引用的对象方法区中类静态属性引用的对象,例如Java类的引用类型的静态变量。
  • 方法区中的常量引用的对象,例如字符串常量池String Table存在的引用。
  • 所有被同步锁synchronized 所持有的对象。
  • Java虚拟机内部的引用,例如:基本数据类型对应的Class对象一些常驻的异常对象(NullPointerException、outofMemoryError)、==类加载器 ==。
  • 反映Java虚拟机内部情况的JMXBeanJVMTI中 注册的回调、本地代码缓存等等。

总结①

除了堆空间 的一些结构,例如:虚拟机栈本地方法栈方法区字符串常量池等地方对堆空间进行引用的,都可以拿来作为GC root进行可达性分析。

除了这些固定的GC Root集合之外,根据用户所选择的GC收集器以及当前回收的内存区域不同,还可以有其他对象临时性 的加入,共同构成完整的GC Root集合,例如:分代收集局部回收(PartialGC)

由于root采用了栈方法存放变量和指针,若是一个指针,则保存了堆内存的对象,但是自己又不存放在堆内存里面,那么其就是一个Root
注意:若要使用可达性分析来判定内存是否可以被回收,那么分析工作必须要在一个能保证一致性的快照中进行。若无法满足这点则分析结果的准确度存疑。
以上这点也是导致GC进行时必须stop the world 的一个重要原因,即使是在号称(几乎)不会发生停顿的CMS收集器中,枚举根节点也是必须要停顿的。

8、对象的finalization机制

Java语言提供了对象终止(finalization)机制来允许开发人员 提供对象被销毁之前的自定义处理逻辑 。

若GC发现没有引用指向一个对象,也就是:GC该对象之前,总是会调用该对象的finalize方法。

finalize方法允许在子类中被重写,用于在对象被回收之前进行资源的释放。通常会在这个方法中进行一些资源释放和清理的工作,例如:关闭文件IO流、套接字或者是数据库连接等等。

注意

永远不要 主动调用某一个对象的finalize方法,应该交给GC使用,原因如下:

  • finalize方法时可能会导致该对象复活。
  • finalize方法执行的时候时间是没有保障的,完全由GC线程来决定,在某些极端情况下,若不发生GC,则finalize完全没有执行的机会;由于优先级比较低,即使主动调用了这个方法,也不会因此直接进行回收。
  • 一个糟糕的finalize会严重的影响GC性能。

从功能角度来说,finalize类似C++的析构函数,但是Java采用的是基于GC的自动内存管理机制,所以finalize方法在本质上不同于C++中的析构函数。

由于finalize方法的存在,虚拟机中的对象一般处于三种可能的状态。

生存还是死亡?

若从所有的根节点都无法访问到某一个对象的时候,说明对象已经无法使用了,则一般来说该被GC回收。

但是实际上 ,一个无法触及的对象可能在某一个条件下复活自己,若是这样,GC回收就是不合理的,因此,定义虚拟机中的对象可能的三种状态如下:

  • 可触及的:从根节点开始,可以到达的对象。
  • 可复活的:对象的所有引用都被释放了,但是对象可能在 finalize中复活。
  • 不可触及的:对象的finalize方法被调用了,并且没有复活,那么进入不可触及状态。不可触及对象不可能被复活,因此finalize只会被调用了一次

以上的三种状态中,由于finalize方法的存在,只有在对象不可触及的时候才能被GC。

具体的过程如何?

判定一个对象objA是否能被GC,至少需要经历两次标记过程

  • 若对象objA到达GC Root没有引用链,则进行了第一次的标记过程。
  • 开始进行筛选,判定该对象是否有必要执行finalize方法。
    • 若对象objA没有重写finalize方法,或者finalize方法已经被虚拟机调用过了,则虚拟机将会视之为没有必要执行,objA判定不可触及
    • 若对象objA重写了 finalize方法,并且还没有执行过,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize方法执行。
    • finalize方法是对象逃脱或者 死亡的最后机会,稍后GC会对F-Queue队列中的对象进行了第二次标记;若objA在finalize方法中引用链上的任何一个对象建立了联系,那么在第二次标记时,objA会被移除即将GC的集合。在这之后,对象再次出现没有引用的情况,这种情况下,finalize方法不会被再次调用,对象直接变成不可触及 的状态,也就是说,一个对象的finalize方法只会被调用一次
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值