概述:java虚拟机对内存处理上主要做两件事,为对象分配内存和回收废弃的对象占用的内存,根据java的内存中的区域划分可以了解,JVM中主要占内存的是堆,因此GC主要处理的也是堆的回收。
1、如何判断哪些对象已废弃不用?
(1)引用计数算法。
即在创建对象的时候会给该对象维持一个引用计数器,当引用一次则+1,引用失效则-1,在许多语言中都用
的是引用计数算法来清理内存,然而在java中却无法解决循环依赖的问题。
(2)可达性分析算法
以一系列的“GC ROOT”对象为起始点,向下搜索,如果可以到达某个对象,则不销毁,如果任何一个GC
ROOT对象都不能到达某对象,则认为此对象不可用(一般情况下,并没有立刻销毁,仅做了标记)。
可作为GC ROOT对象有:
虚拟机栈(栈帧中的局部变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中引用的对象
可以看到这些对象都是内存中线程私有的,这些区域中内存的生命周期都是跟线程相同的,该内存存在,则
说明线程并未结束,而未结束的线程在引用堆中的某个对象,这个对象当然不是不可用的状态。
(3)对象的销毁
在可达性分析算法中,判断某个对象不可达时,并没有立即销毁对象,而是会对它进行一次标记和一次筛选,
筛选分两个条件,首先判断该对象是否覆盖了finalize()方法,然后判断finalize()方法是否被虚拟机调用过,若有则
不执行销毁程序,而是将它放入一个名为F-Queue的队列中。稍后GC会对F-Queue队列中的对象进行第二次小规模
的标记,如果在调用了finalize()方法的对象又一次被引用,则放弃执行,并移出垃圾回收集合,否则执行。
注意:在执行销毁程序的时候,GC并不会等待该程序执行结束,以妨在该销毁出错时,其他对象都处于等待
状态,造成内存溢出。
对象的自我拯救示例:
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 mehtod executed!"); FinalizeEscapeGC.SAVE_HOOK = this; } public static void main(String[] args) throws Throwable { SAVE_HOOK = new FinalizeEscapeGC(); //对象第一次成功拯救自己 SAVE_HOOK = null; System.gc(); // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no, i am dead :("); } // 下面这段代码与上面的完全相同,但是这次自救却失败了 SAVE_HOOK = null; System.gc(); // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else { System.out.println("no, i am dead :("); } } }
运行结果:
finalize mehtod executed!
yes, i am still alive :)
no, i am dead :(
以上代码需注意一点:两段代码相同,但是一次自救成功,一次自救失败,是因为finalize()方法只会被
虚拟 机调 用一次,当面临下一次回收时,finalize()方法不会被执行,被直接销毁了。
2、回收方法区。
方法区在一些人认为是永久代,是永远不会被销毁的区域,在JVM规范中也有说可以不要求回收方法
区,因为在方法区中的回收效率一般比较低。在堆的新生代中一般一次回收能回收70%~95%的空间,然而在方 法区却远达不到这个效率。
在方法区中回收的主要是废弃常量和无用的类,回收废弃常量的模式与以可达性回收堆差不多。
3、垃圾回收算法。
(1)标记--清除算法。即对不可用的对象进行标记,然后清除,会造成内存空间不连续。
(2)复制算法。在HotSpot虚拟机中,是将内存分为一个Eden空间和两个surivivor空间,每次使用Eden空间和
一块surivivor空间,回收时将Eden空间和surivivor空间的可用对象拷贝到另一个surivivor空间中,再清理原空间。
(3)标记--整理算法。与标记--清除算法类似,只是清理后,会将可用对象都向一端移动,形成连续的内存空间。
(4)分代收集算法。是前面几种算法的综合,将内存空间分为几块,有新生代和老年代,当新生代对象销毁较多
时就可以采用复制算法处理,老年代对象存活率比较高则可以用标记--整理或标记--清除算法。
4、内存分配和回收策略。
(1)优先分配Eden空间。
(2)大对象直接进入老年代。新生代采用的复制算法,对大对象的操作消耗比较大。
(3)长期存活的对象直接进入老年代。