• JVM 2-垃圾收集及内存分配策略


      Java虚拟机的内存模型分为五个部分,分别是:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区。程序计数器、Java虚拟机栈、本地方法栈都是线程私有的,也就是每条线程都拥有这三块区域,而且会随着线程的创建而创建,线程的结束而销毁。在这几个区域内就不需要过多的考虑回收的问题。

      然而,堆和方法区中的内存清理工作就没那么容易了。 

    堆和方法区所有线程共享,并且都在JVM启动时创建,一直得运行到JVM停止时。因此它们没办法根据线程的创建而创建、线程的结束而释放。

    堆中存放JVM运行期间的所有对象,虽然每个对象的内存大小在加载该对象所属类的时候就确定了,但究竟创建多少个对象只有在程序运行期间才能确定。 
    方法区中存放类信息、静态成员变量、常量。类的加载是在程序运行过程中,当需要创建这个类的对象时才会加载这个类。因此,JVM究竟要加载多少个类也需要在程序运行期间确定。 因此,堆和方法区的内存回收具有不确定性,因此垃圾收集器在回收堆和方法区内存的时候花了一些心思。

      判断对象的死活
      1 引用计数法:给对象添加一个引用计数器每当有一个地方引用它时,计数器就加1,当引用失效时 计数器减1,当计数器为0时表示对象不可能再被使用 表示对象已死。

    这个方法效率很高,但是它无法解决对象之间的互相依赖的问题。

    public class ReferenceCountingGC {
    
        public Object instance = null;
    
        private static final int _1MB = 1024 * 1024;
    
        /** * 这个成员属性的唯一意义就是占点内存,以便在能在GC日志中看清楚是否有回收过 */
        private byte[] bigSize = new byte[2 * _1MB];
    
        public static void testGC() {
            ReferenceCountingGC objA = new ReferenceCountingGC();
            ReferenceCountingGC objB = new ReferenceCountingGC();
            objA.instance = objB;
            objB.instance = objA;
    
            objA = null;//这两个变量 被设置为空,对象不可能再被使用
            objB = null;
    
            // 假设在这行发生GC,objA和objB是否能被回收?
            System.gc();
        }
        public static void main(String[] args) {
            testGC();
        }
    }

      但是运行结果显示并未回收它们,间接说明jvm不是通过引用计数法来判断对象的死活。

      2 可达性分析

        通过一系列 GC Roots对象作为起点,从这些节点向下搜索 搜索的路径称为引用链 当一个对象没有任何一个引用链相连 则说明对象是不可用的。如Object 5,Object 6,Object 7 虽然有互相关联 但是GC Roots 不可达,所以他们都是可以回收的对象。

      

      java中可以作为GC Roots的对象有下面几种

      • Java虚拟机栈所引用的对象(栈帧中局部变量表中引用类型的变量所引用的对象)
      • 方法区中静态属性引用的对象
      • 方法区中常量所引用的对象
      • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

      java里面的各种引用的分类:这几种引用级别由高到低分别为:强引用、软引用、弱引用和虚引用。

        强引用:如:Object object=new Object();那object就是一个强引用了,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

        软引用:软引用就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存 空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

        弱引用:

          如果一个对象只具有弱引用,那就类似于可有可无的生活用品。它比软引用的强度更弱一些当垃圾收集器工作时 无论当前内存是否足够,那些只被弱引用关联的对象都会被回收掉。
        虚引用:虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象 仅持有虚引用,那么它就和没有任何引用一样。

       对象的死亡与拯救

        即使在可达性分析算法中不可达的对象,也不是一定会死亡的,它们暂时都处于“缓刑”阶段,要真正宣告一个对象“死亡”,至少要经历两次标记过程:

        如果对象在进行可达性分析后发现没有与 GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finaliza()方法。当对象没有覆盖finaliza()方法,或者finaliza()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

        如果这个对象被判定为有必要执行finaliza()方法,那么此对象将会放置在一个叫做F-Queue的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它。这里所谓的“执行”是指虚拟机会触发此方法,但并不承诺会等待它运行结束,原因是:如果一个对象在finaliza()方法中执行缓慢,或者发生了死循环(更极端的情况),将很可能导致F-Queue队列中的其它对象永久处于等待,甚至导致整个内存回收系统崩溃。

      

    /**
     * 此代码演示了两点: 
     * 1.对象可以在被GC时自我拯救。 
     * 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
     * @author zzm
     */
    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()方法都只会被系统调用一次,如果对象面临下一次回收,它的finalize()方法不会再被执行,因此第二次逃脱行动失败。因此这个方法是非常不确定的,连是否会被执行都不能确定 因此这个方法无意义的。

      方法区回收 

      很多人以为方法区(或者HotSopt VM中的永久代)是没有垃圾收集的,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且性价比一般较低,在对的新生代生一般能回收70%~95%的空间,而永久代远低于此。

    永久代的垃圾手机主要回收两部分内容:废弃常量和无用的类。 回收废弃常量与回收Java堆中的对象非常相似。以常量池中字面量的回收为例,若字符串“abc”已经进入常量池中,但当前系统没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用该字面量,若发生内存回收,且必要的话,该“abc”就会被系统清理出常量池。常量池中其他的类(接口)、方法、字段的符号引用与此类似。

    无用的类需要满足3个条件:

    (1)该类所有的实例都已经被回收,即Java堆中不存在该类的任何实例;
    (2)加载该类的ClassLoader已经被回收;
    (3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

    虚拟机可以对满足上述3个条件的无用类进行回收,此处仅仅是“可以”,而并不是和对象一样(不使用了就必然回收)

     


  • 相关阅读:
    Linux下命令行解析
    L.append(a)、L+=[a]和L=L+[a]
    python的lambda表达式
    (转载)2012 EEPW ARM DIY功能扩展模块采买建议(ARM)(PCB)
    (转载)EEPW ARM DIY手记之软件设计(一)——MDK_ARM工程的搭建(ARM)(C)
    (转载)EEPW ARM DIY手记之软件设计(二)——LED跑马灯实验(ARM)(C)
    (转载)EEPW ARM DIY手记之硬件功夫(三)——硬件焊接功夫与硬件测试心得(ARM)(PCB)
    面向对象设计大作业第二阶段:图书馆查书、借书、还书
    C博客作业05指针
    DS博客作业04图
  • 原文地址:https://www.cnblogs.com/tjqBlog/p/10498120.html
Copyright © 2020-2023  润新知