垃圾标记回收和内存分配策略

 垃圾的标记阶段:对象存活判断

引用计数算法

         此方法和操作系统中文件系统判文件存活很相似:在对象中添加一个引用计数器,每当有一个地方引用它时,它便让引用计数器的值加一;相反,当引用消失的时候就减一。任何时刻引用计数器的值为零的对象就不能再使用。

    优点:1、实现简单,垃圾对象便于辨别;                                                                                               2、判定效率高,回收没有延迟性。   

    缺点: 1、需要单独的字段存储计数器,增加了存储开销(空间);                                                        2、每次赋值都需要更新计数器,增加了时间开销(时间);                                                        3、无法处理循环引用的情况,此为致命性的缺陷,故在java领域,至少主流的jvm里面都没有选用引用计数法来管理内存。


正常情况


内存泄漏

        原因解析:虽然断开了p1对象外部指针链接,但是3个对象仍处于循环调用阶段,其引用计数器的值仍然为1,故不可进行标记回收,最终造成内存泄漏。

python如何解决循环引用?

       1、手动解除:在合适的时机,解除引用关系。                                                                              2、使用弱引用weakref(为Python提供的标准库,旨在解决循环引用)。    

可达性标记算法

        该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象(类似于数据结构判断连通图一样,即被判断对象是否是ROOT的子孙节点)。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,第一次标记后,如果有必要执行finalize()方法则对象可以逃脱“死亡”。但是,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

GC Roots的选取

       “GC Roots”根集合就是一组必须活跃的引用。由于Roots采用栈方式存放变量和指针,所以如果一个指针指向了堆里面的对象,但是自己又不存放在堆内存里,则其为一个Roots。

        在java技术体系里面,固定可作为GC Roots的对象包括以下几种:                                                1、在虚拟机栈(栈帧中的局部变量表)中引用的对象,如:各个线程被调用的方法栈中使用到的参数、局部变量、临时变量等。                                                                                                2、在方法区中类静态属性引用的对象,如java类中的引用类型静态变量。                                  3、在方法区中常量引用对象,如:字符串常量池(String table)里的引用。                              4、在本地方法栈中JNI(通常所说的Native方法)引用对象。                                                      5、jvm内部的引用,如:基本类型数据类型所对应的Class对象,还有一些常驻的异常对象(OutOfmemoryError)等,还有系统类加载器。                                                                               6、同步锁所持有的对象。

finalize()方法的使用时机

       任何对象的finalize()方法都只可被系统调用一次。finalize()方法是对象逃脱死亡的最后机会。最好不要主动调用finalize()方法,应交给垃圾回收机制调用,理由如下:

        1、finalize()使用时可能会使对象复活(即由第一次标记阶段到可能与其他对象恢复引用关系)。                                                                                                                                                2、finalize()方法的执行时没有保障的,它会完全由GC线程决定。如果不发生GC,就可能没有执行的机会了。                                                                                                                        3、finalize()可能严重的影响GC的性能,因为其需要重写。如果出现死循环或者其他的极端情况,可能导致整个内存回收子系统的崩溃。  

关于System.gc()

        System.gc()调用时会显示的触发FullGC(),即会对整个堆空间进行垃圾回收。同时对新生代和老年代进行回收,尝试释放被丢弃对象占用的内存。然而,System.gc()的调用附带一个免责声明,无法保证对垃圾收集器的调用,只是提醒需要进行GC了。

内存溢出和内存泄漏

        内存溢出(OOM):没有空闲内存,并且垃圾收集器也无法提供更多的内存。                                           原因:1、java虚拟机的堆内存设置不够                                                                                                    2、代码中创建大量大对象,且长时间不能被垃圾收集器收集(即存在                                               被引用)

        内存泄漏:严格来说:只有对象不会再被程序用到了,但是GC又不能回收                                                    宽泛而言:一些不太好的时间会导致对象的生命周期变得很长,甚至会导致                                                  OOM

        java中内存泄漏举例:

                1、在一个类中声明很多的静态变量 static  xxxx 。由于其属于类的静态变量,随着类的存在而存在、消失而消失,所以会有很长的生命周期,可能会导致内存溢出。                                         2、单例模式中,单例的生命周期和应用程序一样大,所以单例程序中,若持有外部对象的引用的话,那么这个外部对象是不能回收的,则会导致内存泄漏

单例持有外部对象



垃圾回收阶段与垃圾收集算法

分代收集理论

        当前商业虚拟机的垃圾收集器 ,大多遵循了“分代收集”的理论进行设计,它建立在三个分代假说之上:

        1、弱分代假说:大多数对象都是朝生夕灭的。                                                                            2、强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。                                                3、跨代引用假说:跨代引用相对于同代引用来说占少数。   

        这三个假说共同奠定了多款常用的垃圾收集器的一直的设计原则:收集器应该将java堆中划分出不同的区域 ,然后根据回收对象的年龄分配到不同的区域中存储。大多数对象都是朝生夕灭的,可以为其划分新生代区域;而对于多次垃圾收集都难以消亡的对象,可以为其划分老年代区域。

垃圾收集算法:清除阶段

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

        首先标记出所有的需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收掉所有未被标记的对象。虚拟机采用的是标记所有被引用的对象,即可达对象。这是因为大多数对象都是要被回收的,那么只标记存活的对象就可以节省开销。

        优点:实现简单,容易理解

        缺点:1、执行效率不稳定 ,标记和清除两个过程的执行效率会随着对象数量增长而降低                     2、内部空间的碎片化问题,标记、清除之后会产生大量的不连续的内存碎片,碎                            片太多可能导致当以后的程序运行过程中需要分配大对象是无法找到足够的连                            续的内存而不得不进行另外的垃圾收集动作。

标记--复制算法(copying)

           为了解决标记--清除算法面对大量可回收对象时执行效率低的问题,Fenichel提出一种“半区复制”的垃圾收集算法,它可将内存按容量划分为大小相等的两块,每次只使用其中一块。当一块内存用完了,就将还活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。实际上会将内存划分为一块较大的Eden空间和两块较小的Survivor空间,比例为8:1:1,也即每次新生代可用内存空间为整个新生的容量的90%(Eden加上一块Survivor)。现在的商用jvm大多数都使用这种收集算法去回收新生代

            优点:每次都是针对整个半区进行回收,分配内存时就不用考虑空间碎片的复杂情况,只需要移动指针,按顺序分配就行(内存分配可采用指针碰撞的方式)。实现简单, 运行高效。

            缺点:1、该算法的代价是将可用内存缩小为原来的一半,空间浪费太多(现在多用                                   8:1:1的分配策略)。                                                                                                                 2、如果系统中的垃圾对象多,复制算法不会很理想,因为复制算法要求需要复                               制的存活对象不会太多,或者说非常低才行。

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

        该算法的标记过程仍然与“标记--清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向内存空间一端移动,然后直接清理边界以外的内存。标记--清除算法和标记--整理算法的本质差异在于前者是一种非移动的回收算法,而后者是移动式的。

标记整理前内存图


标记整理后内存图

        老年代一般由标记--清除或者标记--整理混合实现垃圾收集。

        Mark阶段的开销与存活对象数量成正比。                                                                                      Sweep阶段与所管理区域大小成正比。                                                                                           Compact阶段的开销与存货对象的数据成正比。

三种垃圾收集算法比较

        

对比图

           从效率上来说,复制算法最高,但是却浪费了太多内存;尽量兼顾上图中的三个指标,标记--整理算法最为平滑,但是效率较差,比复制算法多了一个标记 阶段,比标记--清除算法多了一个内存整理的阶段。

增量收集

            增量收集是针对垃圾收集时的“stop the world”而设计的方式,在增量收集方式中,垃圾收集只收集一小片区域的内存空间,接着切换到应用进程,依次反复,直到垃圾收集完成。增量收集的目的是为了对线程间冲突妥善处理,允许GC线程以分阶段的方式标记、清除或复制工作。

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