GC机制

一、JVM GC回收哪个区域内的垃圾?

==需要注意的是,JVM GC只回收堆区和方法区内的对象。==而栈区的数据,在超出作用域后会被JVM自动释放掉,所以其不在JVM GC的管理范围内。
Java方法区(如HotSpot虚拟机中的元空间或者永久代),很多人认为该部分的内存是不用回收的,java虚拟机规范也没有对该部分内存的垃圾收集做规定,但是方法区中的废弃常量和无用的类还是需要回收以保证永久代不会发生内存溢出。

方法区的垃圾收集主要回收两部分内容:废弃常量不再使用的类

判断废弃常量:如果常量池中的某个常量没有被任何引用所引用,则该常量是废弃常量。

判断不再使用的类(需同时满足下面三个条件):

  1. 该类的所有实例都已经被回收,即java堆中不存在该类的实例对象。
  2. 加载该类的类加载器已经被回收。
  3. 该类所对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射机制访问该类的方法。

Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。

二、GC如何判断对象是否需要回收?有几种方式?

1.引用计数法

给对象添加一个计数器,每当有一个地方引用它时,计数器值就加1,;当引用失效时,计数器值就减1;任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它所引用的所有对象实例的引用计数器减1。
缺点:无法解决对象之间循环引用的问题。

2.可达性分析法

可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个GC ROOT对象作为起点,从这个节点开始向下寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

在这里插入图片描述

在Java语言中,可作为GC Roots的对象包括下面几种:
  a) 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  b) 方法区中类静态属性引用的对象;
  c) 方法区中常量引用的对象;
  d) 本地方法栈中JNI(Native方法)引用的对象。

三、垃圾回收算法

1、标记 - 清除算法

标记-清除算法对需要回收的对象进行标记,标记完毕后,对所有被标记的对象统一进行回收。

在这里插入图片描述

优点: 标记-清除算法不需要进行对象的移动,并且仅对需要回收的对象进行处理,在存活的对象比较多的情况下极为高效。

缺点: 由于标记-清除算法只是清理需要回收的对象,并没有对还存活的对象进行整理,标记清除之后会产生大量不连续的内存碎片可能导致后续程序需要 分配较大对象时,无法找到足够大小的连续内存提前触发垃圾回收动作

2.标记-复制算法

复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉;

在这里插入图片描述

优点: 存活对象较少时,复制效率高;不用考虑内存碎片
缺点: 空间牺牲很大,可用内存仅为实际内存的一半;存活对象较多时,复制效率较低

3.标记整理算法

标记-整理算法采用 标记-清除 算法一样的方式进行对象的标记、清除,但在回收需清理对象占用的空间后,会将所有存活的对象往左端空闲空间移动,并更新对应的指针。标记-整理 算法是在标记-清除 算法之上,又进行了对象的移动排序整理,因此成本更高,但却解决了内存碎片的问题。

在这里插入图片描述

缺点:在标记-清除 算法之上,又进行了对象的移动排序整理,因此成本更高;
优点:解决了“标记-清除“ 算法的内存碎片的问题

4.分代回收算法

当代主流虚拟机(Hotspot VM)的垃圾回收都采用“分代回收”的算法。

“分代回收”是基于这样一个事实对象的生命周期不同,所以针对不同生命周期的对象可以采取不同的回收方式,以便提高回收效率。
Hotspot VM将堆内存划分为不同的物理区,就是“分代”思想的体现。如图所示,JVM内存主要由新生代、老年代、永久代构成。

在这里插入图片描述

(1)新生代(Young Generation):大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。

新生代内又分三个区:一个Eden区,两个Survivor区(一般而言),大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区的存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。

Q: 新生代什么时候发生GC回收?

新生代Eden区中剩余空间不够分配时,此时将发生Minor GC

(2)老年代(Old Generation):在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代,该区域中对象存活率高。
新生代Eden区中剩余空间不够分配时,此时将发生Minor GC存活对象将放入Survivor区若Survivor区不足以容纳这些存活对象,将通过分配担保机制直接提前转移到老年代

老年代的垃圾回收(又称Major GC)通常使用“标记-清理”或“标记-整理”算法。整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。

整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。

Q: 老年代什么时候发生GC回收?

在新生代进行Minor GC之前,如果虚拟机检查发现 老年代是没有足够的空间来容纳全部的新生代对象,就会提早进行一次老年代的回收,防止新生代下次进行GC的时候发生晋升失败。

(3)永久代(Perm Generation):主要存放元数据,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大。相对于新生代和年老代来说,该区域的划分对垃圾回收影响比较小。

四、内存分配与回收策略

1.对象优先在Eden区分配

​ 大多数对象在新生代中被创建,当Eden区没有足够空间进行分配时,虚拟机将发起一次垃圾回收(Minor GC)

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

​ 对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代。“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。

3.大对象直接进入老年代

大对象是指 需要大量连续内存空间的java对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组;

经常出现大对象会导致虚拟机在内存还有不少空间时就提前触发垃圾回收,以获取足够的连续内存来分配给它们;

​ HotSpot虚拟机提供了-XX:PretenureSizeThreshold参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作。

4.空间分配担保(新生代晋升担保)

​ 在新生代进行Minor GC之前,虚拟机必须检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果不够的话, 就提早进行一次老年代的回收,防止新生代下次进行GC的时候发生晋升失败。

**Q:**为什么是新生代所有对象的总空间大小?

新生代使用复制算法,只使用 一个Survivor区来做轮换;一共有多少对象会在这次回收中活下来在实际完成内存回收之前是无法明确知道的,最极端的情况就是内存回收后新生代中所有对象都存活;

因此当出现大量对象在Minor GC后仍然存活的情况,就需要老年代进行分配担保,把Survivor区无法容纳的对象直接转移到老年代。

当Survivor空间不足以容纳一次Minor GC之后存活下来的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保(Handle Promotion)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值