深入理解java虚拟机---垃圾收集器和分配策略-1

博文重点:

    学习目标:哪些内存需要回收

         什么时候回收

            如何回收

    在基于概念讨论的模型中,主要对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 }            
View Code

 

                可达性分析算法:思路,选择一系列称为"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 }
View Code

 

    方法区(永久代)的回收:主要回收废弃常量和无用类。

                废弃常量:eg:"abc"存在常量池中,但没有其它地方引用这个常量,类,方法,字段的符号引用也和这个类似。

                无用类:该类所有实例已被回收

                    加载该类的ClassLoader已被回收

                    对应的Class对象没有被引用,无法在其它地方通过反射访问该类的方法。

                      满足了这些条件的类可以被回收,是否进行回收,取决于我们对虚拟机的参数设置情况。

                使用场景:在大量使用反射,动态代理,CGLib等频繁定义自ClassLoader的场景都需要虚拟机具备类卸载的功能

 

posted @ 2018-02-01 11:32  rose^Chen  阅读(140)  评论(0编辑  收藏  举报