zoukankan      html  css  js  c++  java
  • JVM--一文读懂垃圾回收

    转:https://www.cnblogs.com/kubidemanong/p/9461755.html

    与其他语言相比,例如c/c++,我们都知道,java虚拟机对于程序中产生的垃圾,虚拟机是会自动帮我们进行清除管理的,而像c/c++这些语言平台则需要程序员自己手动对内存进行释放。
    虽然这种自动帮我们回收垃圾的策略少了一定的灵活性,但却让代码编写者省去了很多工作,同时也提高了很多安全性。(因为像C/C++假如你创建了大量的对象,但却由于自己的疏忽忘了将他们进行释放,可能会造成内存溢出)。

    何为垃圾?

    刚才说了,虚拟机会自动帮助我们进行垃圾的清除,那什么样的对象我们才可以称为是垃圾对象呢?
    假如你创建了一个对象

    Man m = new Man();
    

    你用一个变量指向了这个对象,显然对于这个对象,你可以用变量m对这个对象进行利用,但过了一段时间,你执行了

    m = null;
    

    并且也并没有新的变量来指向刚才创建的对象。此时对于这个没有任何变量指向的对象,你觉得它还有用处吗?
    显然,对于这种没有被变量指向的对象,它是一点卵用也没有的,它只能在随风漂流。
    因此,对于这样的对象,我们就可以把它称为垃圾了,它早晚会被垃圾回收器给干掉。

    怎么知道它已经是垃圾对象了?

    假如代码是你自己编写的,你可能知道这个对象啥时候应该被抛弃,你可以随时让它成为垃圾对象。
    但是,你毕竟是你,虚拟机则没那么智能。那虚拟机是如何知道的呢?
    上面已经说了,没有变量引用这个对象时,它就是垃圾对象了,基于这个原理,我们可以这样做啊:
    我们可以为这个对象设置一个计数器,初始值为0,假如有一个变量指向它,那么计数器就加1,如果这个变量不在指向它了,计数器就减1。那么我们就可以判断,如果这个计数器为0的话,那它就是垃圾对象了,否则就是有用的对象。
    对于这种方法,我们称之为引用计数法

    好吧,我们先来夸一夸引用计数法这种方法:
    1.实现简单。
    2.效率高(一个if语句就能解决的问题想不高效都难)。
    不好意思,接下来得说说它那个致命的缺点
    实际上,对于这种引用计数的方法,假如它遇到对象互相引用的话,是很难解决的。
    先看一段代码:

    Man m1 = new Man();
    Man m2 = new Man();
    //互相引用
    m1.instance = m2;//假设Man有instance这个属性
    m2.instance = m1;
    m1 = null;
    m2 = null;
    System.gc();//按道理对象应该被回收
    

    这段代码m1和m2都指向null了,按道理两个对象已经是无用对象,应该被回收,但是,两个对象之间彼此有一个instance的属性互相牵引的对方,导致两个对象并没有被回收。
    这个缺点够致命吧?
    所以,虚拟机并没有采用这种引用计数的方法。

    可达性分析

    除了这种方法,我们还有其他的方法吗?
    答案是有的,必须得有啊。这种方法就是传说中的可达性分析,(我靠,听名字是真的高级啊)。它的工作原理是这样的:
    在程序开始时,会建立一个引用根节点(GC Roots),并构建一个引用图。当需要判断谁是垃圾时,我们可以从这个根节点进行遍历,如果没有被遍历到的节点则是垃圾对象,否则就是有用对象。如下图:


    这个方法可以解决循环相互引用的问题,但是这个方法并没有引用计数法高效,毕竟要遍历图啊。
    总结下判断是否为垃圾对象的算法:
    1.引用计数法。
    2.可达性分析。

    何时进行垃圾回收

    可能有人会觉得这个问题很奇怪,觉得看到垃圾就回收不是很好。对于这个我只能说:
    1.看到房间有一点垃圾你会马上扫?还是等到某个时间点或者当垃圾积累到一定的数量再扫?
    2.虚拟机可没那么智能可以马上识别这个对象是垃圾对象,它还得遍历所有对象才能知道有哪些是垃圾对象。
    所以说,你总不能几秒(我们假设几秒是贼短的时间)就让虚拟机遍历一下所有对象吧?

    这里先说明一下,当垃圾回收器在进行垃圾回收的时候,为了保证垃圾回收不受干扰,是会暂停所有线程的,此时程序无法对外部的请求进行响应。(因为你想啊,当你在可达性分析的时候,那些引用关系还在不断着变化,那不很难受)。
    而且频繁的垃圾回收,对于有一些程序,是很影响用户体验的,例如你在玩游戏,系统动不动就停顿一下,怕你是要把这游戏给删了。
    所以说,垃圾回收是会等到内存被使用了一定的比例的时候,才会触发垃圾回收。至于这个比例是多少,这可能就是人为规定的了。

    怎么回收?

    当我们标记好了哪些是垃圾,想要进行回收的时候,该怎么回收比较好呢?
    可能有一些人就觉得奇怪,这还不简单,看见它是垃圾,直接回收不就得了。
    其实这也不无道理,简单粗暴,直接回收。
    是的,确实有这样的算法,看哪些是被我们标记的垃圾,看见了就直接回收。这种算法我们称之为标记—清除算法
    标记-清除算法工作原理:就是先标记出所有需要回收的对象,然后在统一回收所有被标记过的对象。
    不过,那些人你可别得意啊,因为这种方法虽然简单暴力,但它有个致命的缺点就是:
    标记清除过后,会产生大量的不连续内存碎片,如果不连续的碎片过多的话,,可能会导致有一些大的对象存不进去。这样,会导致下面两个问题:

    1.有些内存浪费了。
    2.对象存不进去,会又一次触发垃圾回收。

    复制算法

    为了解决这种问题,另外一种算法出现了—-复制算法。就是说,它会将可用的内存按容量划分成两块。然后每次只使用其中的一块,当这一块快用完的时候,就会触发垃圾回收,它会把还存活的对象全部复制到另外一块内存中去,然后把这块内存全部清理了。
    这样,就不会出现碎片问题了。
    居然帮我们解决了我们必须夸一下它:不仅帮我们解决了问题,而且实现上也简单、运行也高效。
    但是(凡事都有个但是的),它也是有缺点的,缺点很明显,发现了没有。假如每次存活的对象都很少很少,那另外一块内存不是几乎没有用到?所以说,这种方法有可能导致另外一半内存几乎没用了。内存那么宝贵,这可是很严重的问题。

    优化策略:可以告诉你,有研究显示,其实有98%的对象都是朝生夕死的,也就是说,每次存活的对象确实很少很少。既然我们都知道存活的对象很少很少了,那我们干嘛还1:1的比例来分配?所以说,HotShot虚拟机是默认按8:1的比例来分配的。这样,就不会出现很多内存没用到的问题了。
    可能有人会说,万一占比为1/9的内存不够用了怎么办?不就没地方存那些活的对象?实际上,当内存不够用时,可以向其他地方借些内存来使用,例如老年代里的内存。

    这里说明一下新生代和老年代:说白了,新生代就是刚刚创建不久的对象,而老年代是已经活了挺久的对象。也就是说,有一些对象是确实活的比较久的,对于这种对象,我们另外给它分配内存来养老,而且垃圾回收时,我们不用每次都来这里查找有没垃圾对象,因为这些对象是垃圾的几率会比较小。

  • 相关阅读:
    面试题-酷家乐面试准备
    英语学习-第一次申请试译的小透明(未完待续)
    操作系统educative版本-笔记1
    周末日记-第一次相对正规的技术教学
    资料推荐-一个神奇的网站educative.io
    面试题-持续集成
    每天5分钟玩转容器技术-读书笔记-第六章
    每天5分钟玩转容器技术-读书笔记-第四章到第五章
    工作日记-文件柜驱动层开发总结
    DP套题练习1
  • 原文地址:https://www.cnblogs.com/heishuichenzhou/p/10813771.html
Copyright © 2011-2022 走看看