GC

查看GC日志时需要用到的虚拟机参数:

-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如2017-12-13T12:07:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径

1.引用计数算法

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

/**
 * VM Args:-XX:+PrintGCDetails
 */
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;

        System.gc();
    }

    public static void main(String[] args) {
        ReferenceCountingGC.testGc();
    }
}

运行结果:

[GC [PSYoungGen: 6759K->712K(38400K)] 6759K->712K(124928K), 0.0011986 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 712K->0K(38400K)] [ParOldGen: 0K->610K(86528K)] 712K->610K(124928K) [PSPermGen: 2910K->2909K(21504K)], 0.0099723 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 2330K [0x00000007d5c00000, 0x00000007d8680000, 0x0000000800000000)
  eden space 33280K, 7% used [0x00000007d5c00000,0x00000007d5e46810,0x00000007d7c80000)
  from space 5120K, 0% used [0x00000007d7c80000,0x00000007d7c80000,0x00000007d8180000)
  to   space 5120K, 0% used [0x00000007d8180000,0x00000007d8180000,0x00000007d8680000)
 ParOldGen       total 86528K, used 610K [0x0000000781400000, 0x0000000786880000, 0x00000007d5c00000)
  object space 86528K, 0% used [0x0000000781400000,0x0000000781498b00,0x0000000786880000)
 PSPermGen       total 21504K, used 2927K [0x000000077c200000, 0x000000077d700000, 0x0000000781400000)
  object space 21504K, 13% used [0x000000077c200000,0x000000077c4dbf70,0x000000077d700000)

GC日志分析:

[GC [PSYoungGen(使用PSYoungGen作为年轻代的垃圾回收器): 6759K(年轻代垃圾回收前的大小)->712K(年轻代垃圾回收以后的大小)(38400K)(年轻带的总大小)] 6759K(堆区垃圾回收前的大小)->712K(堆取垃圾回收后的大小)(124928K)(堆区总大小), 0.0011986 secs(回收时间)] [Times: user=0.00(Young GC用户耗时) sys=0.00(Young GC系统耗时), real=0.00 secs(Young GC实际耗时)] 
[Full GC [PSYoungGen(年轻代): 712K->0K(38400K)] [ParOldGen(年老代): 0K->610K(86528K)] 712K->610K(124928K) [PSPermGen(持久代): 2910K->2909K(21504K)], 0.0099723 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
下面则是堆的具体信息,暂时笔者还未能解读

从上述的运行结果可以看到,进行GC后,年轻代的空间被GC进行了极大的清理,则说明objA和objB之间的循环引用并没有影响到GC回收,说明虚拟机采用的并不是引用计数算法。

2、可达性分析算法

在可达性分析算法中,一个很重要的概念就是“GC Roots”,"GC Root"顾名思义即是GC进行的根,GC会判断堆中的对象是否与“GC Roots”能够通过某条线路连接到。如下图所示:


gc-root.png

图中object1~object4都与GC Roots能够连接到,因此不能回收,可以存活,而object5~object7无法连接,因此是可以回收的。
可以作为GC Roots的对象:
1、栈中引用的对象。根据内存模型知道,栈中是正在运行的线程存放的数据,因此当正在运行的程序在引用的对象不能回收,不然会造成问题。
2、方法区中静态属性引用的对象。静态属性会在堆中存一份对象数据,等待调用,这个也是不允许回收的。
3、方法区中常量引用的对象。方法区中有运行时常量池,在常量池中引用的对象也是不允许回收的。
4、本地方法栈中引用的对象。

3、强、软、弱、虚引用

无论用引用计数算法判断对象的引用数量,还是可达性分析算法判断引用链是否可达,都需要判断对象的引用。JDK1.2后,将引用分为四类:强引用(Strong Reference),软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)。这四种引用强度依次减弱。
强引用:普通的引用方法。例如new一个对象,Object obj = new Object();只有在这种引用关系失效的时候,GC才会考虑回收这个对象。
测试代码:

/**
 * VM args:-XX:+PrintGCDetails -XX:+PrintAssembly
 */
public class StrongReferenceTest {

    public static void main(String[] args) {
        Object referent = new Object();

        /**
         * 通过赋值创建strongReference,此时new Object对象同事被两个变量引用到
         */
        Object strongReference = referent;

        System.out.println(referent);
        System.out.println(referent.equals(strongReference));
        referent = null;

        System.gc();

        System.out.println(strongReference);
    }
}

测试结果:

java.lang.Object@4b1210ee
true
[Full GC (System.gc()) [Tenured: 0K->712K(87424K), 0.0029994 secs] 2795K->712K(126720K), [Metaspace: 3210K->3210K(1056768K)], 0.0030517 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
java.lang.Object@4b1210ee
Heap
 def new generation   total 39424K, used 1754K [0x0000000081400000, 0x0000000083ec0000, 0x00000000ab800000)
  eden space 35072K,   5% used [0x0000000081400000, 0x00000000815b6850, 0x0000000083640000)
  from space 4352K,   0% used [0x0000000083640000, 0x0000000083640000, 0x0000000083a80000)
  to   space 4352K,   0% used [0x0000000083a80000, 0x0000000083a80000, 0x0000000083ec0000)
 tenured generation   total 87424K, used 712K [0x00000000ab800000, 0x00000000b0d60000, 0x0000000100000000)
   the space 87424K,   0% used [0x00000000ab800000, 0x00000000ab8b2078, 0x00000000ab8b2200, 0x00000000b0d60000)
 Metaspace       used 3232K, capacity 4494K, committed 4864K, reserved 1056768K
  class space    used 354K, capacity 386K, committed 512K, reserved 1048576K

软引用:比强引用稍弱的引用方法。当某个对象只有这一个软引用存在,且内存不足的时候,GC会考虑回收这部分资源。
如:

Object obj = new Object();
SoftReference<Object> softReference = new SoftReference<>(obj);

当使obj = null时,就只有softReference引用了Object对象,如果内存不足,则GC就会释放这部分资源,当内存充足时,这部分资源会依然存在。
测试代码:

/**
 * VM args:-XX:+PrintGCDetails
 */
public class SoftReferenceTest {

    public static void main(String[] args) {

        Object referent = new Object();

        /**
         * 通过赋值创建softReference
         */
        SoftReference<Object> softReference = new SoftReference<Object>(referent);

        System.out.println(referent.equals(softReference.get()));
        referent = null;

        System.gc();
        System.out.println(softReference.get());
    }
}

测试结果:

true
java.lang.Object@4b1210ee

弱引用:比软引用强度更弱。不管内存够不够用,在下一次GC时,发现某对象只有弱引用时,GC会清理释放这部分资源。
测试代码:

/**
 *
 */
public class WeakReferenceTest {

    public static void main(String[] args) {
        Object referent = new Object();

        /**
         * 通过赋值创建weakReference
         */
        WeakReference<Object> weakReference = new WeakReference<Object>(referent);

        System.out.println(referent.equals(weakReference.get()));
        referent = null;

        System.gc();

        System.out.println(weakReference.get());
    }
}

测试结果:

true
null

虚引用:是最弱的一种引用类型。GC回收时对于虚引用会当做没有引用存在一样。
测试代码:

/**
 * phantom reference 的 get 方法永远返回 null
 * PhantomReference 唯一的用处就是跟踪 referent何时被 enqueue 到 ReferenceQueue 中.
 * 当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收,
 * 这时可以做一些合适的清理工作. 将一个 ReferenceQueue 传给一个 Reference 的构造函数,
 * 当对象被回收时, 虚拟机会自动将这个对象插入到 ReferenceQueue 中,
 * WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的 entries.
 */
public class PhantomReferenceTest {
    public static void main(String[] args) {

        Object referent = new Object();

        /**
         * 通过赋值创建phantomReference
         */
        PhantomReference<Object> phantomReference = new PhantomReference<>(referent,new ReferenceQueue<Object>());

        /**
         * phantom reference 的 get 方法永远返回 null
         */
        System.out.println(referent.equals(phantomReference.get()));
        referent = null;

        System.gc();
        System.out.println(phantomReference.get());
    }
}

测试结果:

false
null

引用的用途

WeakReference:当对对象结构和拓扑不是很清晰的时候,可以通过弱引用,可以合理的释放对象,而不会造成不必要的内存泄漏。
如:

A a = new A();
WeakReference wr = new WeakReference(a);
//B b = new B(a);

引用关系如下图:


image.png

当使a=null时,A就只有弱引用依赖,因此GC会立刻回收A这个对象。
SoftReference:软引用有个很重要的特性就是在内存充足时会保留资源,当内存不足时会释放资源,因此很适合用来做缓存处理。
摘取网络上的一个应用softReference的代码:

public class ImageLoader {  
      
    private Map<String,SoftReference<Bitmap>> cacheImage = new HashMap<String,SoftReference<Bitmap>>();  
      
    public void loadImage(final String path,final Callback callback){  
        SoftReference<Bitmap> softReference = cacheImage.get(path);  
        if(softReference!=null){  
            Bitmap bm = softReference.get();  
            if(bm!=null){  
                callback.execute(bm);  
                return;  
            }  
        }  
        new Thread(new Runnable() {  
           public void run() {  
            HttpClient client = new DefaultHttpClient();  
            try {  
                HttpResponse response = client.execute(new HttpGet(path));  
                HttpEntity entity = response.getEntity();  
                byte []bs= EntityUtils.toByteArray(entity);  
                final Bitmap bm = BitmapFactory.decodeByteArray(bs, 0,bs.length);  
                SoftReference<Bitmap> reference = new SoftReference<Bitmap>(bm);   
                cacheImage.put(path,reference);  
                callback.execute(bm);  
                entity.consumeContent();  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            }  
        }).start();  
    }  
      
    public static abstract class Callback{  
        abstract void execute(Bitmap bm);  
    }  
}

这个就是用软引用来达到缓存处理的方法,这个在andriod中应用得较多,然而有人发现这个方法处理缓存并不是很理想,因此慢慢也在被人抛弃。因为当有10个对象只有软引用时,GC不知道到底clear哪几个或者keep哪几个,甚至,gc会不知道到底是clear资源还是扩展heap。
PhantomReference:虚引用要和ReferenceQueue搭配使用。
在构造Reference对象时,有两种构造函数可供选择:

/* -- Constructors -- */

    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

一种是带ReferenceQueue的,另一种不带此参数。
根据http://blog.csdn.net/u012332679/article/details/57489179所说:

ReferenceQueue这个类有什么用呢,它跟Reference有什么关系呢,关系主要体现在这几个方面,首先Reference这个类里面在构造函数的时候有两种选择,一种是给它传入一个ReferenceQueue,一种是不传,如果不传的话,等这个对象的内存被回收了,直接从Active变为Inactive状态,如果我们传入了ReferenceQueue,那么当对象的内存回收的时候会经历一个过程,从Active->Pending->Enqueued->Inactive。pending状态就是等待着进入ReferenceQueue队列的这样一个状态,说白了它目前还没被回收,只是对象的引用(用户代码中的引用)被移除了,pending保存了这个引用,回收的过程中,ReferenceHandler这个线程会把该对象的引用(pending)放入到我们在构造函数时传入的那个队列里面

ReferenceQueue就是当一个对象gc调后,对象的信息会再保存一段时间,以便我们能够进行额外的操作。
有一个问题:当在ReferenceQueue中发现SoftReference或WeakReference对象时,并不能确定该对象引用的对象已被销毁,此时的对象只是进入了Finalizable状态,然而如果使用的是PhantomReference,在ReferenceQueue中发现了PhantomReference对象,此时referent则已经销毁了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Java 虚拟机有自己完善的硬件架构, 如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系...
    尹小凯阅读 1,678评论 0 10
  • JVM runtime 数据区-- 程序计数器(PC)-- 方法区------ 打印GC详情 -XX:+Print...
    zhj_njuer阅读 410评论 0 0
  • 参数设置 在Java虚拟机的参数中,有3种表示方法用“ps -ef |grep "java"命令,可以得到当前Ja...
    九问阅读 9,112评论 2 52
  • 人生并不像火车要通过每个站似的经过每一个生活阶段。人生总是直向前行走,从不留下什么。 —— 刘易斯 GC日志理解 ...
    guqj阅读 270评论 0 1
  • 以为花钱加了个写作群就能写出东西来,有严格的群规把守就能坚持日更。结果第二天,拖拉至深夜,匆匆拉过一篇旧文,点两点...
    立笔阅读 332评论 2 0