Java堆内存垃圾回收机制(GC)详解 (引用计数法,根搜索法详解)

什么对象需要回收
在Java堆中存放着Java程序中新建的对象,也就是我们开发过程中 new,newarray 创建的对象。但没有明确的代码去释放他们。垃圾回收机制就是释放这些不再被程序所使用的对象的过程。

为什么要使用垃圾收集器
在虚拟机的堆内存中,当某些对象失去引用,对于虚拟机内存而言就是”垃圾”,虚拟就就会回收这部分内存,以供后续的新对象使用。除了释放不在被引用的对象外,
在堆空间中,并不是所有的内存空间都是连续的,因为在程序运行过程中每次新建对象都会申请堆内存,并不能保证内存可以整齐的码放在你面前。要是申请不到大片连续的内存区域,还可能造成“内存不足”的假象。

引用计数法
引用计数法是垃圾回收的早期策略,在堆中的每个对象都有一个引用来计数,每当有一个地方引用到它时,计数器的值就会加一;引用失效时,计数器值减一,当计数器的值为0时,就认为该对象就是不被使用的,可以进行垃圾回收,但这种算法缺钱,就是很难解决对象之间相互循环引用的问题;

public static void main(String[] args) {

        GcObject obj1 = new GcObject(); //Step 1
        GcObject obj2 = new GcObject(); //Step 2

        obj1.instance = obj2; //Step 3
        obj2.instance = obj1; //Step 4

        obj1 = null; //Step 5
        obj2 = null; //Step 6
    }
}

class GcObject{
    public Object instance = null;
}

百度图

 当程序进行到Step 1 时 obj1 的计数器=1;
 当程序进行到Step 2 时 obj2 的计数器=1;
 程序进行到Step 3 时 obj1 的计数器=2;
 程序进行到Step 4 时 obj2 的计数器=2;

此时obj1,obj2的技术都为2;
当程序进行到Step 5 时 obj1 的计数器-1 结果为1;
当程序进行到Step 6 时 obj2 的计数器-1 结果为1;如下图
这里写图片描述
那么如果采用的引用计数算法的话 GcObject实例1和实例2的计数引用都不为0,两个实例所占内存不到释放,于是就产生了内存泄露。

根搜索法(可达性算法)
根搜索法是通过一系列称为”GC Root”的对象作为起点。以这些节点作为根节点向下搜索,搜索过程中的路径成为“”引用链“”,当某个对象到”GC Root”没有任何引用链时,也就是“根对象”不可达。就被认为是可以被回收的对象,
“”GC Root“”对象包括以下几种:
虚拟机栈中的引用的对象;
方法去中类静态引用的对象;
常量引用的对象;
这里写图片描述
如上图中,GC Root对object1,object2,object3,object4都是仍存活的对象,object5,object6,object7都是GC Root 触及不到的,因此都时要被回收打的内存。

对于垃圾收集器力来说,堆中每个对象都有三种状态,可触及的,可复活的,不可触及的。不可触及的也就是我们刚提的 GC Root引用链连接不到的对象。对于另外的两种,可触及打的,是指每个对象从他生命的开始起,只要程序至少保留一个可触及的引用,那么它就是可触及的,一旦程序释放了所有对该对象的引用,那么这个对象就成了可复活状态。
若满足以下条件则为可复活状态:
   从根节点连接不到,但是可能在垃圾收集器执行某些终结方法时触及;**不仅仅是声明了finalize()方法的对象,而是所有的对象都要经过可复活状态** 
   先来说finalize()方法,如果类中声明了此方法,垃圾回收器(GC)会在释放这个实例占据的内存空间之前,执行这个方法如下
class Final(){
protected void finalize(){
   System.out.println("Final obj die!");   //go die
   }
}

根搜索算法中不可达对象在回收之前,要进行二次标记。
第一次标记时会进行一次筛选:筛选的条件是是否有必要执行finalize()方法。
当对象没有覆盖finalize()方法,或者finalize()被虚拟机调用过,则虚拟机认为没有必要执行finalize()方法。
如果这个对象有必要执行finalize(),则会放在一个队列里,以一个低优先级的线程进行执行finalize()方法进行二次标记,如果在finalize()方法中,对象重新回到引用链上(比如this赋值给其他引用链上的对象),则该对象不被回收,而移出该队列。
注意:finalize()方法只被调用一次,如果这个对象在GC时被调用过一次finalize()方法,则第二次GC的时候,就会被判断为没有必要执行finalize()而被直接回收。
另外finalize()能做的所有工作,都可以通过try-finally更好、更及时的解决。所以请忘掉finalize().



    public class TestGc {  
        public static TestGc HOOK;  

        @Override  
        protected void finalize() throws Throwable {  
            super.finalize();  
            TestGc.HOOK = this;  
            System.out.println("finalize");  
        }  

        /* 
              输出结果 
        finalize 
        HOOK is alvie 
        HOOK is dead 
         */  
        public static void main(String[] args) throws Exception {  
            HOOK = new TestGc();  
            HOOK = null;  
            System.gc();//第一次GC,符合有必要要执行finalize的条件  
            Thread.sleep(500);//Finalizer线程优先级较低,所以要等一会  
            if (HOOK != null) {  
                System.out.println("HOOK is alvie");  
            } else {  
                System.out.println("HOOK is dead");  
            }  

            HOOK = null;  
            System.gc();//第二次GC,因为已经执行过一次finalize,所以没有必要进行二次标记  
            Thread.sleep(1000);//Finalizer线程优先级较低,所以要等一会  
            if (HOOK != null) {  
                System.out.println("HOOK is alvie");  
            } else {  
                System.out.println("HOOK is dead");  
            }  
        }  

    }  

无论是引用计数法,还是跟搜索法都是引用操作,那么就有可能产生垃圾问题,但是对于引用也需要有一些合理化的设计。在很多的时候并不是所有的对象都需要被我们一直使用,那么就需要对引用的问题做进一步的思考。从JDK1.2之后关于引用提出了四种类型的引用:
demo 引自http://blog.csdn.net/qq_34280276/article/details/52863626;
●强引用:也就是我们new出来的对象当内存不足的时候,JVM宁可出现OutOfMemory错误停止,也需要进行保存,并且不会将此空间回收(永远不会被GC回收);

public class Demo{
    public static void main(String[]args){
        Object obj=new Object();//强引用,默认
        Object ref=obj;//引用传递
        obj=null;//断开连接
        System.gc();
        System.out.println(ref);
    }
}

●软引用:当内存不足的时候,进行对象的回收处理,往往用于高速缓存中;

public class Demo{
    public static void main(String[]args){
        Object obj=new Object();
        SoftReference<Object> ref=new SoftReference<Object><obj>;//软引用
        obj=null;//断开连接
        System.gc();
        System.out.println(ref.get());
    }
}

如果此时内存空间充足,那么对象将不会回收,如果空间不充足,则会进行回收。

public class TestDemo{
    public static void main(String[]args){
        Object obj=new Object();
        String str="hello";
        obj=null;//断开连接
        SoftReference<Object> ref=new SoftReference<Object><obj>;//软引用
        try{
            for(int x=0;x<Inter.MAX_VALUE;x++){
                str+=str+x;
                str.intern();
            }
        }catch(Throwable e){

        }
                   System.out.println(ref.get());
    }
}

●弱引用:无论内存够不够,下一次内存收集之前都会进行;

弱引用本质的含义指的是说只要一进行GC处理,那么所引用的对象将会被立刻回收。弱引用需要使用的是Map接口的子类:java.util.WeakHashMap。
范例:观察弱引用

package cn.test.demo;
import java.lang.ref.SoftReference;

public class TestDemo{
    public static void main(String[]args){
        String key=new String(“hi”);
        String value=new String(“hello”);
        Map<String,String> map=new WeakHashMap<String,String>();
        map.put(key,value);
        System.out.println(map.get(key));
        key=null;
        System.out.println(map);
        System.gc();
        System.out.println(map);
    }
}  

一旦出现GC,则必须进行回收处理,而且一回收一个准。
HashMap与WeakHashMap区别?
HashMap是强引用,而WeakHashMap是弱引用。

在java.lang.ref包中存在有一个WeakReference的一个子类。
范例:观察WeakReference

package cn.test.demo;
import java.lang.ref.SoftReference;

public class TestDemo{
    public static void main(String[]args){
        String key=new String(“hi”);
        WeakReference<String> map=new WeakHashMap<String>(key);
        Key=null;
        System.out.println(ref.get());
        System.gc();
        System.out.println(ref.get());
    }
}

弱引用之所以不敢轻易使用的原因,就是因为其本身一旦有了GC之后就会立刻清空,这个对于程序的开发不利。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值