<深入Java虚拟机>之1.2:垃圾回收机制

    垃圾回收一般是在Java堆,方法区中进行,因为堆中几乎存放了Java中所有的对象实例。我们讨论的垃圾回收机制一般仅仅作用于jvm堆区和方法区,因为它们是thread共享的,这部分内存的分配和回收都是动态的。

    1⃣️.如何确定某个对象是“垃圾”?

    在所有语言中,几乎都要涉及对象是否存活的问题,比如C++/OC中给对象添加了一个引用计数机制,当需要的时候,这个引用的计数器就会+1;当引用失效的时候它的计数器-1;当计数器=0时,说明该对象不可能再被使用。但是引用计数机制有个问题,就是很难发现,解决循环计数问题:


pic1.B为A的子对象

    程序创建了一个指针,指向了一个计数为1的A对象,对象A创建了对象B(计数器也为1)作为组合的子对象。对象A在创建完了,也会创建对象B,因为依赖,所以对象A拥有对象B的强引用(指针)。现在如果子对象B也有一个指向对象A的强引用(指针),那么A的计数器会变为2,如下图pic2.


pic.2 

    当有一天pointer不需要A对象了(调用A的析构函数),A的counter值变为1,不过由于对象B的计数值仍然为1,两个对象的计数值!=0,所以都没有释放掉,造成内存泄露。


pic.3 循环引用的内存泄露

    C++和OC也都有各种方法解决这个问题,自动化的智能指针/arc技术等,但是还是会遇到更棘手的循环计数问题。所以Java的用了更加适合,方便的清理技术。

    Java中采取了 可达性分析法。该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。可达性分析法具体是如何操作的,我觉得是图论中的连通性分析算法。(P.S. GC Roots = jvm栈的引用对象 or 方法区static对象 or 方法区常量 or JNI的引用对象)。

   2⃣️.典型的垃圾收集算法

    1.Mark-Sweep(标记-清除)算法

    标记-清除算法是最基础的收集算法,它分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的根搜索算法中判定垃圾对象的标记过程。

回收之前
pic4.标记清除算法,回收之后

这个算法有如下缺点:

    -标记和清除过程的效率都不高。

    -标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不触发另一次垃圾收集动作。


    2.Copying(复制)算法

   复制算法是针对标记—清除算法的基础上进行改进而得到的,它将jvm内存按容量分为大小相等的两块(但是实际应用中不是),每次只使用其中半块,当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面,然后再把已使用过的内存空间一次清理掉。

复制算法优点是 一.简单,只需移动栈顶指针,按顺序分配内存即可。 二. 高效, 每次只对一块内存进行回收。三. 不会有内存碎片问题。

缺点很明显,可一次性分配的最大内存缩小了一半。


copying 算法

    3.Mark-Compact(标记-整理)算法

    为了解决Copying算法的缺点,充分利用内存空间,有了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后去除掉端边界以外的内存。具体过程如下图所示:

标记-整理算法

    4.Generational Collection(分代收集)算法

    分代收集算法是目前Hotspot垃圾收集器采用的算法。它将内存划分为若干个不同的区域:分为老年代(Tenured Generation)和新生代(Young Generation),在新生代中,每次垃圾收集时都会发现有大量对象死去(90%以上),只有少量存活,因此可选用复制算法来完成收集,不会有太大的复制开销,而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。

新生代:新创建的对象都存放在这里。因为大多数对象很快变得不可达,所以大多数对象在年轻代中创建,然后消失。这里的垃圾清除叫“minor GC”。

年代:没有变得不可达,存活下来的年轻代对象被复制到这里。因为它更大的规模,GC发生的次数比在年轻代的少。这里的垃圾清除叫“major GC”(或“full GC”)发生了。其速度一般会比Minor GC慢10倍以上。另外,如果分配了Direct Memory,在老年代中进行Full GC时,会顺便清理掉Direct Memory中的废弃对象。

永久代(permanent generation)也称为“方法区(method area)”,他存储class对象和字符串常量,静态变量。不是永久的存放从老年代存活下来的对象的。在这块内存中有可能发生垃圾回收。发生在这里垃圾回收也被称为Full GC。

   3⃣️. 年轻代组成部分

    -对象优先在Eden分配。

    -大对象直接进入老年代。

    -长期存活的对象将进入老年代。

    年轻代总共有3块空间,其中2块为Survivor区。


年轻代

  执行顺序如下:

绝大多数新创建的对象分配在Eden区。

在Eden区发生一次GC后,存活的对象移到其中一个Survivor区。

在Eden区发生一次GC后,对象是存放到Survivor区,这个Survivor区已经存在其他存活的对象。

一旦一个Survivor区已满,存活的对象移动到另外一个Survivor区。然后之前那个空间已满Survivor区将置为空,没有任何数据。

经过重复多次这样的步骤后依旧存活的对象将被移到老年代。当对象在Survivor区躲过一次GC的话,其对象年龄便会加1,默认情况下,如果对象年龄达到15岁,就会移动到老年代中。

 4⃣️.典型的垃圾收集器

1.Serial/Serial Old  2.ParNew 3.Parallel Scavenge 4.Parallel Old 5.CMS

6. G1:G1是jdk1.7的新的收集器,替换1.5的CMS,其具有并发,分代收集,空间整合,可预测停顿时间模型等特点的新一代收集器技术。

代码来讲解:

public class Main

{

    public static final int_1MB=1024*1024;

    public static voidmain(String[] args)

    {

        byte[] a1,a2,a3,a4;

        a1 =new byte[2*_1MB];

        a2 =new byte[2*_1MB];

        a3 =new byte[3*_1MB];

        a4 =new byte[4*_1MB];

    }

}

结果:[GC (Allocation Failure) [PSYoungGen: 5439K->464K(9216K)] 5439K->4568K(19456K), 0.0037039 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]

Heap

PSYoungGen      total 9216K, used 7922K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)

eden space 8192K, 91% used [0x00000007bf600000,0x00000007bfd48af8,0x00000007bfe00000)

from space 1024K, 45% used [0x00000007bfe00000,0x00000007bfe74010,0x00000007bff00000)

to  space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)

ParOldGen      total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)

object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)

Metaspace      used 2918K, capacity 4494K, committed 4864K, reserved 1056768K

class space    used 324K, capacity 386K, committed 512K, reserved 1048576K

(VM参数是:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8)

    从结果来看,分配a4的时候发生了一次minor gc,这次gc结果是年轻代从5439K变为464K。a1,a2,a3占用了7MB,新生代一共10MB,所以a4要分配进来,必须发生minor gc,但是此时又发现survivor区不能放进a1或者a2或者a3(1MB大小),所以只好通过分配担保机制前转移到老生代去。gc结束后,4MB的a4放入老生代。

    5⃣️.空间分配担保

    在发生gc之前,vm先检查老年代最大的可用连续空间是否大于年轻代所有对象总空间。如果条件成立,minor gc确保成功,如果不成立vm看HandlePromotionFailure设置的值是否允许担保失败,如果允许,那么会检查老年代最大可用的连续空间是否大于历次老年代对象的平均值,大于就进行minor gc,小于就该为full gc。

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

推荐阅读更多精彩内容