JVM之垃圾回收

来源:互联网 发布:淘宝送装入户 编辑:程序博客网 时间:2024/06/05 00:57


GC需要完成的3件事情 



哪些内存需要回收?

在隔离区的程序计数器、虚拟机栈、本地方法栈三个区域随着线程而生而灭。每一个栈帧中分配多少内存基本上都是固定已知的,因此这几个区域的内存分配和回收具有确定性,就不多考虑内存回收的问题了,在方法或线程结束的时候就自然回收了这部分的内存。
在java堆和方法区中,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间才能知道会创建那些对象,这部分内存的分配和回收是动态的,GC所关注的就是这部分内存。

什么时候回收?

回收第一件事要搞明白就是,在堆中,哪些对象时“存活”的,哪些“死去”的不再被任何途径使用的对象。
两种存活判断方法:引用计数算法、可达性分析算法;
引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被引用的。弊端:不能解决对象间相互循环引用的问题,所以java虚拟机不采用。
采用的一般有:微软的COM(Component Object Model)技术、使用ActionScript3的FlashPlayer、Python语言等进行内存管理。
可达性分析算法:通过一系列的称为“GC roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain,简称RC),当一个对象到GC Roots没有任何RC相连,证明此对象时不可用的。现在主流的商用语言(java、C#等)都是使用可达性分析算法。

在java语言中,可作为GC Roots的对象包含有
虚拟机栈中的引用对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(即一般说的Native方法)引用的对象;

生存还是死亡?

即使在可达性分析算法中不可达的对象,也并非是必须被回收,可以缓一缓,对象可以进行一次自救。在垃圾回收清理内存之前先调用一次finalize()方法。

public class FinalizeEscapeGC {                public static FinalizeEscapeGC SAVE_HOOK=null;                public void isAlive(){                System.out.println("yes, i am still alive");        }                @Override        protected void finalize() throws Throwable{                                super.finalize();                System.out.println("finalize method executed!");                //对象重新建立 GC root 连接链                FinalizeEscapeGC.SAVE_HOOK=this;        }                public static void main(String[] args) throws Throwable{                SAVE_HOOK = new FinalizeEscapeGC();                                //对象第一一次成功拯救自己;                SAVE_HOOK =null;                                System.gc();                                //因为fianlize方法优先级很低,所以暂停0.5秒等待他                Thread.sleep(500);                                if(SAVE_HOOK!=null){                                                SAVE_HOOK.isAlive();                                        }else{                        System.out.println("no,i am dead :(");                }                                //下面这段代码与上面的完全相同,但是这次自救却失败了;-------------------finalize方法只会被调用一次;        SAVE_HOOK =null;        System.gc();        //因为fianlize方法优先级很低,所以暂停0.5秒等待他            Thread.sleep(500);           if(SAVE_HOOK!=null){                                SAVE_HOOK.isAlive();                                }else{                        System.out.println("no,i am dead :(");                }                                }         }

对象自救描述:

1、判断是否存在对象与GC Root的reference chain:

               如果引用链存在:回收内存;

               2、如果不存在:第一次进行标记,并且进行筛选(筛选条件:是否有必要执行finalize()):

                             没必要执行:当对象没有覆盖finalize()或finalize()方法已经被调用过一次,回收内存;

                             3、有必要执行:将对象放到F-Queue的队列中,自动创建线程finalizer,第二次小规模标记;

                                            finalize()未执行完,自救失败:回收内存

                                            4、建立引用链,自救成功:不回收


如何回收?

垃圾收集算法

标记-清除算法(mark-Sweep)



    不足:两个过程,效率不高;

    清除之后会产生大量不连续空间碎片,从而导致在程序运行时,无法找到足够连续内存,不容易给大对象分配空间,而出发另一次垃圾收集动作。

    标记-清除算法是算法的基础,因为这个算法有以上不足,以下算法都是针对这些特点进行修改和优化。

复制算法(copying)

    

    不足:对象存活率较高时,频繁进行复制操作,效率变低;如果不想浪费50%的空间,就必须额外的空间进行分配担保;

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

    

    和标记-清理算法标记过程相同,但不会直接清理掉对象,让所有存活的对象向一端移动,直接清理掉端边界外的内存。

分代收集算法

java堆分为新生代和老年代,新生代根据其特点,使用复制算法,只需付出少量存活对象复制的成本,而老年代特点是对象存活率时间长,没有额外空间做担保,只能选择标记-清理或标记整理



原创粉丝点击