Java垃圾回收

java虚拟机的生命周期:

启动一个java程序时,一个虚拟机实例开始诞生,程序关闭的时候,虚拟机实例也随之消亡。

对象死亡判断方法

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。

1、引用计数算法:

  • 给对象增加一个计数器
  • 对象每被引用一次,计数器+1,计数器数值越大说明对象引用越频繁。
  • 每当引用失效,计数器 -1;
  • 任何时候计数器为0的对象就是不可能再被使用的

优点:算法简单,效率高
缺点:不能帮助解决相互引用对象的回收问题。

在这里插入图片描述
所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。

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;
    }
}

2、可达性分析

这个算法的思想是通过一系列称为“GC ROOTs”的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到GC ROOTs没有任何引用链的话,就证明这个对象是不可用的,需要被回收。

分析过程

在垃圾回收器进行垃圾回收时,它首先从一组GC ROOTs开始遍历这些根对象可以访问到的对象引用,并构建出一个存活对象的图。然后遍历这个对象图,标记所有可达的对象。所有未被标记的对象都被视为不再使用的对象,可以被回收。

哪些对象可以作为GC ROOTs

  • 虚拟机栈(栈帧中的局本部变量表)中引用的对象
  • 本地方法栈(Native方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中类常量引用的对象
  • 所有被同步锁持有的对象
  • JNI(Java Native Interface)引用的对象

对象可以被回收就代表一定会回收吗?

即使在可达性分析中不可达的对象,也并非“非死不可”的,这个时候它们正处于“缓刑阶段”,要真正宣告一个对象的死亡,至少要经过两次标记过程,可达性分析中不可达的对象被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没必要执行。

被判定为需要执行的对象会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会真的被回收。

finalize方法在JDK 9及后续版本被舍弃。

Java引用类型

无论是“引用计数法”判断对象引用数量还是“可达性分析”判断对象的引用链是否可达,判断对象的存货都与“引用”相关。
在JDK 1.2 之后 Java 对引用的概念进行了扩充,将引用分为以下四类:

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)

强引用

Java里的引用默认就是强引用,任何一个对象的赋值操作对产生了这个对象的强引用,例如:

Object obj = new Object();

强引用的特性是,只要有强引用存在,被引用的对象就不会被垃圾回收。

软引用

软引用是由java.lang.ref.SoftReference所提供的功能,意思是只有在内存不足的情况下,被引用的对象才会被回收。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

弱引用

弱引用是由java.lang.ref.WeekReference所提供的功能,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。

虚引用

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。虚引用是由java.lang.ref.PhantomReference所提供的关联功能。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

垃圾收集算法

1.标记-清除算法

分为“标记”和“清除”两个阶段
第一步:发生垃圾清理操作时,将可以被回收的对象进行标记处理。
第二步:回收第一步中标记的对象所占的空间。
优点:算法简单。
缺点:标记和清除两个过程效率都不高;内存碎片化严重,后续可能会发生大对象找不到可以存放的连续内存空间问题。

2.复制算法

将内存分为大小相同的两块,每次只用其中的一块;当使用的这块内存存满后,将其中还存活的对象复制到另一块内存中,再将之前那块内存进行清理操作。
优点:不容易产生内存碎片。
缺点:内存被压缩到了原来的一半,如果存活对象比较多的话,复制算法效率会下降(不适合老年代)。

3.标记-整理算法:

根据老年代的特点提出的一种标记算法,标记过程与标记清除算法一样,但后续不是直接对可回收对象回收,而是让所有存活对象向内存的一端移动,然后清理掉端边以外的内存空间。

第一阶段:将需要清除的对象进行标记。
第二阶段:将存活的的对象(未被标记的)向内存的一端进行移动,移动完毕后,清除剩下的内存空间。

因为多了整理这一阶段,所以效率也不是很高,适合老年代这种垃圾回收频率不是很高的场景。

4.分代收集:

该算法是目前大部分JVM所采用的的一种垃圾回收算法,根据对象存活周期的不同将内存分为几块。一般将Java堆分为新生代和老年代,这样可以根据各个年代的特点使用不同的垃圾收集算法。

在这里插入图片描述

新生代 :Eden、S0、S1
一般新生代会划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间。

Eden空间比Survivor0Survivor1空间大,这是因为在新生代中大部分的对象被创建后很快就会被GC,所以Survivor空间不用分配太多的空间。

大部分情况,对象首先会在Eden区域分配,在一次新生代垃圾回收后,如果对象还存活,就会进入Survivor 0 或 Survivor 1,并且对象的年龄会+1,当它的年龄达到一定程度(默认15)就会被晋升到老年代。

当进行回收时,将Eden空间和Survivor0空间中还存活的对象复制到另一块Survivor1空间中,
然后清除Eden和Survivor0的空间,之后就使用Eden和Survivor1这两块空间。	
当进行下一次垃圾回收时,将Eden和Survivor1的空间还存活的对象复制到Survivor0,然后清除Eden和Survivor1的空间。	
以此类推,每次总有一个Survivor空间是空的。即新生代一般使用复制算法。

当然,如果目标Survivor空间无法存储Eden和Survivor存活的所有对象时,会将这些对象存储到老生代。
当对象中Survivor区躲过一次GC时,其年龄就会加1,当到达一定的年龄后,会将其移动到老生代。

每次垃圾回收新生代大部分对象被回收老生代少部分对象被回收。

老年代:Tenured
在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到老生代中。
因此,可以认为老生代中存放的都是一些生命周期较长的对象。所以在老生代中一般所采取的垃圾回收算法是标记-整理算法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值