垃圾回收

垃圾回收

一、如何判断对象需要被回收

1.1 引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;当计数器为0,说明对象不在被使用,可以被回收。当计数器非0,说明对象正在被使用,不会被回收。

缺点: 无法解决循环引用。如 ObjectA.instance=B; ObjectB.instance=C; ObjectC.instance=A;这是个循环引用,把A,B,C都置空后是应该是可以被回收的,但是如果用引用计数法将不会回收他们。

1.2 根搜索算法

以一个“GC Roots”为起点,从这个节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用。

如图:


Java中,可作为GC Roots的对象包括以下几种:

1. 虚拟机栈中的应用的对象。(本地变量表中的reference类型)

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

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

4. 本地方法栈JNI的引用的对象。

二、引用

JDK1.2之后,Java对引用进行了扩充,分为

强引用:Strong Reference 类似于Object o=new Object();

软引用:Soft Reference  无内存时回收,可用做缓存

弱引用:WeakReference  触发GC,则回收,可避免内存泄露

虚引用:PhantomReference 用处不大,多为虚拟机底层用到

三、Finalize方法

提到垃圾回收,不得不提finalize方法。针对根搜索算法,垃圾回收需经历两个标记阶段:1. 如果进行根搜索后发现没有引用链,则进行第一次标记,然后进行一次筛选,判断是否需要执行finalize方法。

1.1如果对象覆盖了finalize方法,要处理一些事情,则把对象放入一个F-Queue的队列,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行。

1.2如果对象没有覆盖finalize方法,或则finalize方法已经执行过了,则不进入F-Queue队列。

2. 如果F-Queue有值,则进行第二次标记,如果finalize方法中重新给对象一个引用,则对象被移除F-Queue队列。

实例:

package com.mylearn.jvm;

/**

 * Created by IntelliJ IDEA.

 * User: yingkuohao

 * Date: 13-11-22

 * Time: 上午10:20

 * CopyRight:360buy

 * Descrption:

 * To change this template use File | Settings | File Templates.

 */

public class FinalizeTest {

    public static FinalizeTest finalizeTest = null;

    public static void main(String args[]) {

        finalizeTest = new FinalizeTest();

        test();  //第一次test会执行finalize方法

        test();  //第二次不会执行,finalize方法只执行一次

    }

    private static void test() {

        finalizeTest = null;   //引用释放

        System.gc();          //gc

        try {

            Thread.sleep(500);     //等待 finalizer执行

        } catch (InterruptedException e) {

            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

        }

        if (finalizeTest == null) {

            System.out.println("对象已死");

        } else {

            System.out.println("对象尚存活");

        }

    }

    protected void finalize() throws Throwable {

        super.finalize();

        System.out.println("finalize executed!");

        finalizeTest = this;    //重新给对象一个引用

    }

}

结果:

finalize executed!

对象尚存活

对象已死

大概了解一下,Finalize尽量别用。

四、垃圾回收算法

4.1标记清除

Mark-Sweep算法:分为标记和清除两个阶段:

1. 标记出所有需要回收的对象。

2. 清除掉所有标记的对象。

缺点:

1. 效率太低

2. 标记清除会产生大量的内存碎片。

4.2复制算法

为了解决效率问题,复制算法出现。它将可用内存划分为两块,每次使用一块,当这一块用完了,把存活的对象复制到另一块,然后把使用过的这一块一次性清理。

优点:没有碎片,移动栈顶指针,简单高效。适用于对象存活率很低的区域。

缺点:浪费一半内存

4.3标记整理

Mark-Compact

如果对于对象存活率很高的对象,采用复制算法效率会很低。

1. 标记,同标记清除算法

2. 不去清理,而是让存活的对象都向一端移动,然后清理掉端边界以外的内存。

4.4分代收集

这个是大家常说的一种,其实还是用的前面的三种算法。

由于新生代的对象都是朝生夕死,且存活率非常低,98%的对象都会死。根据复制算法的特点,处理低存活率的对象非常高效。所以新生代就用了复制算法。又由于一半的空间太浪费,而98%对象都会死,所以产生了Survivor FromSurvivor to空间,8:1:1的比例,能更好地利用内存。这样每次只有10%的内存被浪费,而不是50%

年老代的对象存活率很高,不适用复制算法,一般采用标记整理货标记清除算法。

4.5火车算法

火车算法主要是把内存分块,每次处理一块,处理完后再处理下一块,渐进式收集。主要是为了消除用户可察觉到的垃圾回收停顿。相当于是G1收集器的一个基础。

五、垃圾收集器

收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。简单介绍一下:

Serial收集器:

串行收集器,收集的时候停止所有其他工作线程。Stop the World! 不适合做server端。常用作Client模式下的默认新生代收集器,简单而高效。复制算法+串行

ParNew收集器:

并行新生代收集器,跟Serial比起来不过多起了几个GC线程,并行而已。其他一样,但它却是许多运行在Server模式下的虚拟机首选的新生代收集器。复制算法+并行。

Parallel Scavenge收集器:

ParNew类似,也是复制算法,并行的收集器。但它的关注点不同,CMS等收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间,而ParallelScavenge收集器的目标则是达到一个可控制的吞吐量。

停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户的体验;而高吞吐量则可以最高效地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。复制算法+并行+吞吐量优先。

Serial Old收集器

serial收集器的年来代版本,采用标记-整理算法

Parallel Old收集器

ParNew收集器的年老代版本,标记-整理算法+多线程。

CMS收集器

Concurrent Mark Sweep:以获取最短回收停顿时间为目标的收集器。目前很大一部分的应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统的停顿时间最短,以给用户带来较好的体验。用的标记-清除算法。具体包括四个阶段:

1. 初始标记

2. 并发标记

3. 重新标记

4. 并发清除

优点:并发收集,低停顿

缺点:

1. CPU资源敏感

2. 无法处理浮动垃圾

3. 产生空间碎片。

G1收集器

一个概念模型。

1. 基于“标记-整理”算法,不会产生碎片

2. 可以非常精准地控制停顿。

G1将整个Java堆划分为很多大小固定的独立区域,并跟踪他们的额垃圾堆积程度,在后台维护一个列表,每次回收垃圾最多的区域,所以叫做Garbage First

参考:《深入理解Java虚拟机》,《深入Java虚拟机

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值