1.概述
Java内存运行时区域的各个部分里:
其中程序计数器、虚拟机栈、本地方法栈3各区域随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每个栈帧中分配多少内存基本上是在类结构定下来是就已知了,因此这几个区域的内存分配和回收都具备确定性,不需过多考虑。
而堆和方法区不一样,只有在程序处于运行期间才能你该知道创建哪些对象,这部分内存分配和回收是动态的,因此主要关注。
2.对象死亡判别
1.引用计数法
给对象中添加一个引用计数器,每当有一个地方引用时,计数器的值加1,;当引用失效时,计数器值减1,任何时刻计数器值为0的对象就是不会再被使用的对象。其实现简单,效率也较高,但是由于很难解决对象之间相互循环引用的问题。因此主流的Java虚拟机并没有这样来管理内存。
2.可达性分析
主流的实现都是可达性分析。基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点往下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连接,那么证明这个对象是不可用的。
可作为GC Roots的对象包括以下几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象;
2.方法区中静态属性引用的对象;
3.方法去中场量引用的对象;
4.本地方法栈中(即一般所说的Native方法)引用的对象。
原因:GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收。方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为 GC roots,被GC roots引用的对象不被GC回收。
如果要细化GC Roots,GC Roots则包括:
1,所有年老带对象
2,所有全局对象
3,所有jni句柄
4,所有上锁对象
5,jvmti持有的对象
6,代码段code_cache
7,所有classloader,及其加载的class
8,所有字典
9,flat_profiler和management
10,最重要的,所有运行中线程栈上的引用类型变量。
3.引用
无论是引用计数法还是可达性分析,都谈的是“引用”,JDK1.2之前说Java的引用:如果reference类型的数据中存储的数值代表的是另一块内存的钱,就 成为这块内存代表着一个引用。因此JDK1.2后,将引用分为:强引用、软引用、弱引用、虚引用4种。
强引用(Strong Reference):只要强引用还存在,垃圾搜集器永远不会回收被引用对象,如Object obj = new Object();
软引用(Soft Reference):描述一些有用但非必须的对象,在系统将要发生内存溢出时就回收,JDK提供了SoftReference来实现软引用;
弱引用(Weak Reference):用来描述非必须的对象,比软引用弱一些,被弱引用关联的对象只能生存到下次垃圾回收发生之前,垃圾回收发生时,不 论内存是否足够都会被回收,JDK提供了WeakReference来实现软引用;
虚引用(Phantom Reference):也成幽灵引用或者幻影引用,最弱的引用关系。一个对象是否有虚引用的存在完全不会对其生存时间构成影响,也无 法通过虚引用来去的一个对象实例,对对象社只需饮用关联唯一用处就是能在这个对象被收集器回收时收到一个系统通知。JDK提供了PhantomReference 类来实现软引用;
4.生存死亡
要宣布一个对象死亡至少要经历两次标记过程:如果通过可达性分析没有与GC Roots相连接的引用链,会被第一次标记和进行一次筛选。筛选条件是此 对象是否有执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过。虚拟机将这两种情况都视为“没有必要执行”。
如果一个对象有必要执行finalize()方法,会被放在F-Queue队列,并在稍后一个由虚拟机自己建立的、低优先级的Finalizer线程去执行它,虚拟机会触 发这个方法,但是不会等它执行完,否则如果一个对象在finalize()方法中执行缓慢,那么内存回收系统会崩溃。finalize()方法是对象最后一次逃脱死亡机 会。稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象在第二次标记前没能拯救自己就要被回收。拯救办法是只要与引用链上任何一个对象建 立关联即可。
示例代码:
public class FinalizeEscapeGC { public static FinalizeEscapeGC SAVE_HOOK = null; public void isAlive(){ System.out.println("yes,i am steal alive!"); } protected void finalize() throws Throwable{ super.finalize(); System.out.println("finalize method executed."); FinalizeEscapeGC.SAVE_HOOK = this; } public static void main(String[] args) throws InterruptedException{ SAVE_HOOK = new FinalizeEscapeGC(); //对象第一次成功拯救自己 SAVE_HOOK = null; System.gc(); //因为finalize优先级很低,所以暂停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(); //因为finalize优先级很低,所以暂停0.5秒等待它 Thread.sleep(500); if(SAVE_HOOK != null){ SAVE_HOOK.isAlive(); }else{ System.out.println("no, i am dead."); } } }
运行结果:
finalize method executed. yes,i am steal alive! no, i am dead.
因为一个对象的finalize方法只会被系统自动调用一次,如果面临下一次回收,它的finalize()方法不会被再次执行,只有被回收。由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此不要使用。
5.回收方法区
虽然永久代的垃圾回收效率极低,但是也会回收,主要两部分内容:废弃常量呵呵无用的类。
废弃常量的回收和堆中的对象类似,也是看是否有引用。
无用的类的回收需要满足以下3个条件:
1.该类所有的实例已被回收,也就是Java堆中不存在该类的任何实例;
2.加载该类的ClassLoader已被回收;
3.该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的任何实例。
即使满足条件也只是可被回收,并不像对象一定会被回收,是否对类进行回收虚拟机有自己的参数控制。
在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备自动卸载类的功 能,以保证永久代不会溢出。