博文重点:
学习目标:哪些内存需要回收
什么时候回收
如何回收
在基于概念讨论的模型中,主要对Java堆和方法区进行讨论。
why?:一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样。只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,gc关注的就是这一块内存。
哪些内存需要回收:
判断对象是否存活:
引用计数算法:对象中添加一个引用计数器,有一个地方引用它则计数器加1,引用失效时,减1。引用为0的对象就是不可使用的。
优点:实现简单,判定效率高。
缺点:无法解决对象之间的循环引用,见代码。
1 public class ReferenceCountingGC { 2 public Object instance = null; 3 4 private static final int _1MB = 1024 * 1024; 5 6 private byte[] bigSize = new byte[2 * _1MB]; 7 8 public static void testGC() { 9 ReferenceCountingGC objA = new ReferenceCountingGC(); 10 ReferenceCountingGC objB = new ReferenceCountingGC(); 11 objA.instance = objB; 12 objB.instance = objA; 13 14 15 objA = null; 16 objB = null; 17 18 // 虽然引用计数都为1,但内存还是被回收了,说明采用的不是引用计数算法 19 System.gc(); 20 } 21 22 public static void main(String[] args) { 23 testGC(); 24 } 25 }
可达性分析算法:思路,选择一系列称为"GC Roots"的对象作为起始点,从这些节点向下搜索,走过的路就称为引用链。如果一个对象无法通过引用链到达"GC roots",则证明该对象不可用,则可被回收。
可作为GC Roots的对象:虚拟机栈中引用的对象,方法区类静态属性引用的对象,方法区中常量引用的对象,Nativa方法中引用的对象。 todo:理解gc roots
引用:
todo:各种应用场景
引用细化定义:当内存空间还足够,则能保留在内存中。如果内存空间进行垃圾收集之后还是非常紧张,则抛弃这些对象。
基于这样的需求,扩充了引用的概念。
强引用:只要强引用存在,就永远不会被gc。eg. Object obj = new Object();
软引用:内存充足时不会回收,不足时被回收。jvm将这个软引用加入到与之关联的引用队列
弱引用:无论内存是否充足,都会进行回收。jvm将这个弱引用加入到与之关联的引用队列
虚引用:
对象的两次标记:如果对象在进行第一次可达性分析之后,没有到gc roots到引用链,则进行第一次标记。并进入第一次自救过程,如果该对象重写了finalize()方法时 && finalize()方法没有被虚拟机调用过,则会执行finalize()方法进行自救过程,将该对象放入到一个F-Queue到队列中,由虚拟机自动建立的,低优先级的Finalize线程去执行(但是不保证会等待方法运行结束,为了效率考虑)。如果在finalize()方法中将该对象的引用赋值给了类变量或成员变量,重新建立起了可达关系,则在该第二次标记过程会被移出"即将回收"集合,自救成功,但要注意,这样的自救只能执行一次。
1 public class FinalizeEscapeGC { 2 public static FinalizeEscapeGC SAVE_HOOK = null; 3 public void isAlive() { 4 System.out.println("yes , i am still alive"); 5 } 6 7 @Override 8 protected void finalize() throws Throwable { 9 System.out.println("finalize method excute!"); 10 FinalizeEscapeGC.SAVE_HOOK = this; 11 } 12 13 public static void main(String[] args) throws InterruptedException { 14 SAVE_HOOK = new FinalizeEscapeGC(); 15 16 // 第一次拯救自己成功 17 SAVE_HOOK = null; 18 System.gc(); 19 20 Thread.sleep(500); 21 if(SAVE_HOOK != null) { 22 SAVE_HOOK.isAlive(); 23 } else { 24 System.out.println("dead"); 25 } 26 27 // 第二次拯救自己失败,只能执行一次 28 SAVE_HOOK = null; 29 System.gc(); 30 31 Thread.sleep(500); 32 if(SAVE_HOOK != null) { 33 SAVE_HOOK.isAlive(); 34 } else { 35 System.out.println("dead"); 36 } 37 } 38 }
方法区(永久代)的回收:主要回收废弃常量和无用类。
废弃常量:eg:"abc"存在常量池中,但没有其它地方引用这个常量,类,方法,字段的符号引用也和这个类似。
无用类:该类所有实例已被回收
加载该类的ClassLoader已被回收
对应的Class对象没有被引用,无法在其它地方通过反射访问该类的方法。
满足了这些条件的类可以被回收,是否进行回收,取决于我们对虚拟机的参数设置情况。
使用场景:在大量使用反射,动态代理,CGLib等频繁定义自ClassLoader的场景都需要虚拟机具备类卸载的功能