Java垃圾回收机制浅析

在C++中,对象所占的内存在程序结束运行之前会一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾,JVM的一个系统级线程会对该内存块进行自动释放。

垃圾回收优势:

1.垃圾回收能自动释放内存空间,减轻编程的负担,使java程序员在编写程序时不需要再考虑内存管理的问题。

2.垃圾回收机制使java中的对象不再有“作用域”的概念,只有引用的对象才有“作用域”。

3.垃圾回收机制防止了内存泄露,可以使空闲内存得到有效使用。同时,由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片(分配给对象的内存块之间的空闲内存洞),垃圾回收可以清除这些内存记录碎片,也就是将所占用的堆内存移到堆的一端,这样JVM就可以将整理出的内存分配给新的对象。

垃圾回收劣势:

1.垃圾回收机制开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象,而且最终释放没用的对象,该过程需要花费处理器的时间。

2.垃圾回收算法的不完备性。早先采用的某些垃圾回收算法就不能保证100%收集到所有的废弃内存。当然随着垃圾回收算法的不断改进以及软硬件运行效率的不断提升,这些问题都可以迎刃而解。

垃圾判定常用算法:

1.引用计数法(Reference Counting Collector)

实现原理:给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法),具体代码如下图:

扫描二维码关注公众号,回复: 1305861 查看本文章
public class Main {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();

        object1.object = object2;
        object2.object = object1;

        object1 = null;
        object2 = null;
    }
}

class MyObject{
    public Object object = null;
}

将object1和object2赋值为null后,object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。

因此,Java 里没有采用这样的方案来判定对象的“存活性”。

2.tracing算法(Tracing Collector)

实现原理:把所有引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。其余的对象则被视为“死亡”的“不可达”对象,或称“垃圾”。

下图中,object5,object6 和 object7 便是不可达对象,视为“死亡状态”,应该被垃圾回收器回收。

这里写图片描述

在Java语言中,可以作为GCRoots的对象包括下面几种:

(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

(2). 方法区中的类静态属性引用的对象。

(3). 方法区中常量引用的对象。

(4). 本地方法栈中JNI(Native方法)引用的对象。

垃圾收集算法:

由于Java虚拟机规范并没有对如何实现垃圾收集做出明确规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集,所以只列举几种常见的垃圾收集算法。

1.Mark-Sweep(标记-清除)算法

这里写图片描述

标记-清除算法分为两个阶段,第一阶段先标记出所有需要被回收的对象,第二阶段再回收被标记的对象所占用的空间。这种方式简单方便 ,但是容易产生内存碎片。

2.Copying(复制)算法

这里写图片描述
实现原理:Copying把可用内存空间均分为两块,每次只使用其中一块,当这块内存被分配完后,会进行垃圾回收,将当前所有存活对象复制到另外一块内存上,当前内存块则全部清空。

该算法实现简单高效,不会产生内存碎片,但是可用内存缩减为原来的一半,如果存活对象很多,效率会大大降低。所以该算法适合于存活对象少,垃圾多 的情况。

3.Mark-Compact(标记-整理)算法

实现原理:该算法标记阶段与Mark-Sweep(标记-清除)算法一致,但是第二个阶段不是直接清除,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

这里写图片描述

4.Generational Collection(分代收集)算法

实现原理:根据对象的存活周期,将内存划分为不同的区域,一般会划分为老年代(Tenured Generation)和新生代(Young Generation),可以根据不同代的特点采取最适合的收集算法。

老年代:存放存活了一段时间的对象,这些对象早早就被创建了,而且一直活了下来,这些对象被存放在老年代区。每次垃圾收集时只有少量对象需要被回收,一般使用的是Mark-Compact算法。

新生代:存放的是刚刚创建的对象,在代码运行时会持续不断地创造新的对象,这些新创建的对象会被统一放在此区域。每次垃圾回收时都有大量对象需要被回收,一般使用Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少。

而Copying算法是将内存均等分为两份,实际上是存在问题的。对于新生代而言,新对象持续不断地被创建,如果只有一半可用内存,那显然要持续不断地进行垃圾回收工作,反而影响到了正常程序的运行。

因此,实际上我们是按照8:1:1的比例进行划分的,依次取名为 Eden、Survivor A、Survivor B 区,其中 Eden 意为伊甸园,形容有很多新生对象在里面创建;Survivor区则为幸存者,即垃圾回收后仍然存活下来的对象。

工作原理

1.首先,Eden 区最大,对外提供堆内存。当 Eden 区快要满了,则进行Minor GC(从年轻代空间回收内存被称为 Minor GC),把存活对象放入 Survivor A 区,清空 Eden 区,使Eden区继续对外提供堆内存;

2.当 Eden区再次被填满,此时对 Eden 区和 Survivor A 区同时进行 Minor GC,把存活对象放入 Survivor B 区,同时清空 Eden 区和Survivor A 区;

3.Eden区继续对外提供堆内存,并重复上述过程,即在 Eden 区填满后,把 Eden 区和某个 Survivor 区的存活对象放到另一个 Survivor 区;

4.当某个 Survivor 区被填满,且仍有对象未被复制完毕时,或者某些对象在反复 Survive 15 次左右时,则把这部分剩余对象放到老年代区;

5.当老年代区也被填满时,进行 Major GC(从老年代空间回收内存被称为 Major GC)。

猜你喜欢

转载自blog.csdn.net/j1231230/article/details/78296618