JVM垃圾回收机制

如何判断对象已经死去

引用计数法

给对象添加一个引用计数器,每当有一个地方引用他时,计数器就加一,当引用失效时,引用计算器就减一,任何时候计数器为0的对象就是不可能再被使用的
这个方法实现简单,效率高,但是目前主流的虚拟机都没有选择这个算法来管理内存,主要的原因是他很难解决对象之间的循环引用带来的问题。

public class ReferenceCountingGc {
    Object instance = null;
	public static void main(String[] args) {
		ReferenceCountingGc objA = new ReferenceCountingGc();
		ReferenceCountingGc objB = new ReferenceCountingGc();
		objA.instance = objB;
		objB.instance = objA;
		objA = null;
		objB = null;

	}
}
可达性分析算法

目前主流的实现中采用的是可达性分析算法来判断对象是否是存活着的。这个算法的基本思路是通过一系列的被称为GCRoots的对象作为起点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GCRoots没有任何引用链相连的话,则证明这个对象是不可用的。
在这里插入图片描述在java语言中,可以作为GC Roots的对象包含下面这几类

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区的常量引用的对象
  • 本地方法栈中引用的对象
再谈引用

java对引用的概念进行了扩充,将引用分为强引用,软引用,弱引用和虚引用四种

  • 强引用是指在程序代码中普遍存在的,类似于Object i=new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
  • 软引用用来描述一些还有用但是并非必要的对象,对于软引用的对象,在系统将要发生内存溢出之前,会先把这些对象列近回收范围之内进行二次回收
  • 弱引用也是用来描述非必须的对象,被弱引用关联的对象只能够生存到下一次垃圾收集发生之前
  • 虚引用:任何时候都可能被回收
生存还是死亡

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候他们暂时处于缓刑阶段,要真正宣告一个对象死亡,至少要经历两次标记过程,如果对象在进行可达性分析后发现没有与GC Roots相连的引用链,那他将会被第一次标记且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法的,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行
如果被判定为需要执行的对象将会被防止在一个队列中进行二次标记,除非这个对象与引用链上的任何一个对象建立联系否则真的会被清除

回收方法区

方法区永久代的垃圾回收主要回收两部分内容:废弃常量和无用的类

  • 如何判断一个常量是废弃常量
    假如常量池中存在字符串“abc”,如果当前没有任何一个String对象引用该字符串常量的话,就说明废弃常量,如果这时候发生内存回收的话而且有必要的话,“abc”就会被系统清理出常量池
  • 如何判断一个类时无用的类
    需要同时满足一下三个条件
  1. 该类所有的实例都已经被回收
  2. 加载该类的ClassLoader已经被回收
  3. 该类的java.lang.Class对象没有在任何地方引用,无法在任何地方通过反射访问该类的方法

垃圾收集算法

标记清除算法

该算法被分为标记清理阶段,首先标记处所需要回收的对象,在标记完成后统一回收所有被标记的对象,他是最基础的手机算法,后续的算法都是对其不足进行改进得到的,这种垃圾收集算法会带来两个比较明显的问题

  1. 效率问题
  2. 空间问题(标记清理后会产生大量不连续的片段)
    在这里插入图片描述
复制算法

为了解决效率问题,复制收集算法出现了,他可以将内存分为大小相同的两块,每次使用其中的一块,当这一块内存使用完之后,就将还存活着的对象复制到另一块去,然后再把使用的空间一次清理掉,这样就使每次的内存回收都是对内存区间的一般进行回收
在这里插入图片描述

标记整理算法

根据老年代的特点提出一种标记算法,标记过程仍然与“标记-清理”一致,但是后续不是直接对可回收对象回收,而是让所有存活的对象向一端移动,但是直接清理掉边界以外的内存
在这里插入图片描述

分代收集算法

当前虚拟机的垃圾收集算法都采用分代收集器算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块,一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法
比如在新生代,每次收集都会有大量的对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就能够完成垃圾收集,而老年代的对象存活几率较高,而且没有额外的空间对他进行分配担保,所以我们必须选择标记清除或者标记整理算法来进行垃圾收集

HotSpot的算法实现

枚举根节点

从可达性分析中从GC节点找到引用链这个操作为例,可作为GC Roots的节点主要在全局性的引用(例如常量和类静态属性)与执行上下文中(例如栈帧中的本地变量表),如果要逐步检查这里的引用,那么必然会消耗很多的时间。
在执行系统停顿下来后,并不需要一个不漏的检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法得知哪些地方存放着对象引用

安全点

如果为每一条指令都生成对应的oopMap,那将会需要大量的额外空间,这样GC的空间成本会变得很高。实际上hotspot也的确没有为每条指令都生成oopMap,前面已经提到,只是在特定的位置记录了这些信息,这些位置信息被称为安全点,有两种类型的安全点,为抢先式中断和主动式中断

垃圾收集器

Serial收集器(新生代复制算法-老年代标记整理算法)

他的单线程的意思不仅仅以为这他只使用一条垃圾收集线程去完成垃圾收集工作,更重要的是他在进行垃圾收集工作的时候必须暂停其他所有的工作,直到他收集结束
在这里插入图片描述缺点:stop the word会带来很多不良的影响
优点:他简单而且高效,serial没有线程交互的开销,自然可以获得很高的单线程效率,serial收集器对于运行在client模式的虚拟机来说是一个不错的选择

ParNew收集器(新生代复制算法-老年代标记整理算法)

ParNew收集器其实就是serial收集器的多线程版本,除了使用多线程的垃圾收集以外,其余的行为和Serial收集器完全一致
在这里插入图片描述他是许多运行在server模式的虚拟机的首要选择,除了serial收集器以外,只有它能够和CMS收集器配合工作
使用场景:是许多运行在server模式下的虚拟机中的首选新生代收集器

parallel scavenge收集器(新生代复制算法-老年代标记整理算法)

parallel scavenge收集器关注的是吞吐量,cms等垃圾收集器关注点更多的是用户线程的停顿时间,所谓吞吐量就是指CPU用于运行用户代码的时间与CPU消耗总时间的壁纸。Parallel Scavenage收集器提供了供用户找到最适合的停顿时间或者吞吐量
在这里插入图片描述

CMS 收集器(老年代标记清除算法)

CMS收集器是一种以获取最短回收停顿时间为目的的收集器,他非常符合在注重用户体验的应用上使用
CMS收集器是HotSpot第一款真正意义上的并发收集器,他第一次实现了让垃圾收集线程与用户线程(基本上)同时工作
工作可以分为以下四个阶段

  • 初始标记:暂停所有的其他线程,并记录下直接与root相连的对象,速度很快
  • 并发阶段:同时开启GC和用户线程,用于各闭包结构去记录可达对象,但在这个阶段结束,这个闭包结构并不能够保证包好所有的可达对象,因为用户线程可能会不断的更新引用域,所以GC先后才能无法保证可达性分析的实时性,所以这个算法会记录这些发生引用的地方
  • 重新标记阶段就是为了修正并发标记阶段因为用户程序继续运行而导致的标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般比初始阶段的时间稍微长,远远比并发阶段的时间短,
  • 并发清除:开启用户线程,同时GC线程开始对标记的区域做清扫
    在这里插入图片描述优点:并发收集,低停顿
    缺点:对CPU资源敏感,无法处理浮动垃圾,会产生大量的空间碎片
G1收集器

G1收集器是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大内存容量的机器,以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征
G1收集器的特点:

  • 并发和并行
  • 分代收集
  • 空间整理:G1收集器采用标记整理算法
  • 可预测的停留:能够让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N秒,这几乎已经是实时java收集器的特征了
    G1不再将收集范围分为新生代和老年代,他将整个java堆划分为多个大小相等的独立区域,虽然还保留新生代和老年代的概念,但是新生代和老年代不再是物理隔离的了,他们都是一部分人region的集合
    G1收集器之所以能够建立可预测的停顿时间模型,是因为他可以有计划的避免在整个java堆中进行全区域的手机,G1跟踪各个region里面的垃圾堆积的价值大小锁获得的空间大小和回收所需时间的经验值,在后台维护一个优先列表,每次根据允许时间,优先回收最大的Region,保证了G1收集器能够在有限的时间内可以获取尽可能高的收集效率
    在这里插入图片描述

内存分配与回收策略

java技术体系中所长岛的自动内存管理最终可以归结为自动化的解决了两个问题:给对象分配内存以及回收对象分配的内存

对象优先在Eden分配

大多数情况下,对象在新生代区中分配,当eden区域没有足够的空间进行分配时,虚拟机将发起一次Minor GC
Minor GC:指发生在新生代的垃圾收集动作,因为java对象大多数都具备朝生夕灭的特点,所以minor gc非常的频繁,一般回收速度也比较快
major gc/full gc:指的是发生在老年代的GC。出现的同时一般会伴随着minor gc,major gc的速度一般会比minor gc的速度慢上10倍以上

大对象直接进入老年代

所谓的大对象,就是需要大量连续内存的java对象,最典型的大对象就是那种很长的字符串或者数组。大对象会直接在老年代分配,这样做的目的是避免在eden区以及两个survovor区之间发生大量的内存复制(新生代采用复制算法收集内存)

长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在eden出生并经历一次minor gc后仍然存活,并且能够被survivor容纳的话,将被移动到servicor空间中,并且对象的年龄为1,对象在servivor中每经过一次minor gc ,年龄增加1,当增加到一定的程度和,就将会被晋升到老年代中。

动态对象年龄判断

为了能够更好的适应不同程序的内存状况,虚拟机并不是永远要求对象的年龄必须达到maxTenuringThreshold才能够晋升老年代,如果在survivor空间中相同年龄的所有对象的大小总和大于survivor空间的一半,年龄大于或者等于该年龄的对象可以直接进入到老年代

jdk命令行工具
  • jps:用户查看所有的java进程的启动类,传入参数和虚拟机参数等信息
  • jstat:用于手机hotspot虚拟机各方面的运行数据
  • jinfo:显示虚拟机配置信息
  • jmap:生成堆转储快照
  • jhat:用于分析heapdump文件,他会建立一个http/html服务器,让用户在浏览器上分析结果
  • jstack:生成虚拟机当前时刻的线程快照,线程快照就是当虚拟机内每一条线程正在执行的方法堆栈的集合
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值