zoukankan      html  css  js  c++  java
  • 深入理解jvm虚拟机读书笔记-垃圾收集器与内存分配策略(一)

    1.引用计数算法

    给对象一个引用计数器,每当有一个地方引用它时,计数器就加一;当引用失效时,计数器就减一;任何时刻计数器为0的对象就是不可能再被使用。

    优点:实现简单,判定效率很高,微软的COM技术、使用ActionScript 3的FlashPlayer等等都使用了引用计数进行内存管理。

    缺点:Java虚拟机没有使用,最主要的原因是它很难解决对象之间互相循环引用的问题。

    2.可达性分析算法

    Java等主流商用程序语言都是通过可达性分析算法来判断对象是否存活。通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没用任何引用链相连时,则证明此对象是不可用的。

    Java语言中可作为GC Roots的对象包括下面几种:

    • 虚拟机栈中引用的对象。
    • 方法区中类静态属性引用的对象。
    • 方法区中常量引用的对象。
    • 本地方法栈中JNI引用的对象。

    3.Java引用类型

    为什么JDK1.2以后要引入四种引用类型?在此之前,对象只有引用或者没被引用两种状态,对于如何描述一些"食之无味,弃之可惜"的对象无能为力。我们希望描述一些对象:当内存还足够时,能保存在内存中;如果内存空间进行垃圾收集之后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的场景。

    Java中的引用分为:强引用、软引用、弱引用和虚引用。

    • 强引用普遍存在,类似“Object obj = new Object()”,只要强引用还在,垃圾收集器永远不会回收被引用的对象。

    • 软引用是用来描述一些还有用但非必须的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,会把这些对象列入进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

      软引用主要应用于内存敏感的高速缓存,在android系统中经常使用到。一般情况下,Android应用会用到大量的默认图片,这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软引用技术来避免这个问题发生。SoftReference可以解决oom的问题,每一个对象通过软引用进行实例化,这个对象就以cache的形式保存起来,当再次调用这个对象时,那么直接通过软引用中的get()方法,就可以得到对象中中的资源数据,这样就没必要再次进行读取了,直接从cache中就可以读取得到,当内存将要发生OOM的时候,GC会迅速把所有的软引用清除,防止oom发生。

        public class BitMapManager {
            private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
        
            //保存Bitmap的软引用到HashMap
            public void saveBitmapToCache(String path) {
                // 强引用的Bitmap对象
                Bitmap bitmap = BitmapFactory.decodeFile(path);
                // 软引用的Bitmap对象
                SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
                // 添加该对象到Map中使其缓存
                imageCache.put(path, softBitmap);
                // 使用完后手动将位图对象置null
                bitmap = null;
            }
        
            public Bitmap getBitmapByPath(String path) {
        
                // 从缓存中取软引用的Bitmap对象
                SoftReference<Bitmap> softBitmap = imageCache.get(path);
                // 判断是否存在软引用
                if (softBitmap == null) {
                    return null;
                }
                // 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
                Bitmap bitmap = softBitmap.get();
                return bitmap;
            }
        }
      
    • 弱引用也是用来描述非必需对象的,但是他的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论当前内存是否足够都会回收掉只被弱引用关联的对象。

    • 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个持有虚引用的对象,和没有引用几乎是一样的,随时都有可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。

    4.对象的生存和死亡

    在可达性分析中不可达的对象那个,并非是“非死不可”的。要真正宣告一个对象死亡,至少经历两次标记过程:可达性分析后没有与GC Roots相连接的引用链,会被第一次标记并且进行一次筛选,筛选条件是此对象有没有必要执行finalize()方法。对象没有覆盖finalize()方法或者已经被执行过,都会被视为“没有必要执行”。

    没有必要执行finalize()方法的对象将被第二次标记,随后便被回收。如果此对象被判定为有必要执行finalize()方法,那么这个对象将被放置在一个叫做F-Queue的队列中,这个队列将被一个由JVM自动建立,低优先级的Finalizer线程去执行。这里的“执行”只意味着JVM会触发对象的finalize()方法,但不承诺会等待它运行结束,因为如果一个对象在finalize()中执行缓慢,或者发生了死循环,这将可能导致F-Queue队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次标记,如果对象要在finalize()方法中拯救自己,那么它只要重新与引用链上的任意一个对象建立关联即可,

      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()方法都只会被系统自动调用一次。 建议大家不要使用此方法分,运行代价高昂,不确定性大,无法保证各个对象的调用顺序。

    5.回收方法区

    方法区(或者HotSpot虚拟机中的永久代)垃圾收集的“性价比”很低。

    永久带垃圾收集主要回收两部分内容:废弃常量和无用的类。当系统中没有对象引用常量池中的对象时,如果这是发生内存回收,而且必要的话,这些常来那个就会被清理出常量池。常量池中其他类(接口)、方法、字段的符号引用也与此类似。

    判断一个类是否是无用的类,需要满足下面三个条件:

    • 该类所有的实例都已经回收,也就是java堆中不存在该类的任何实例。
    • 加载该类的ClassLoader已经被回收。
    • 该类对应的java.lang.Class对象那个没有在任何地方被引用,无法在任何地方访问该类的方法。

    在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能。

  • 相关阅读:
    Servlet-获取页面的元素的值的方式以及区别
    Http请求-get和post的区别
    Servlet-xml配置简介以及url-pattern简介
    javaweb目录结构简介
    Servlet-生命周期简介
    tomcat-四种运行模式和三种部署模式(优化)
    命名空间System.IO
    Dictionary 字典
    导出-以虚拟表的形式获取数据源
    导入excel-uploadify+npoi
  • 原文地址:https://www.cnblogs.com/ekoeko/p/9658235.html
Copyright © 2011-2022 走看看