2019独角兽企业重金招聘Python工程师标准>>>

1、为什么要进行垃圾回收?

每当在我们写代码的时候,不管是new一个对象,还是引用,还是填充数据到数组,都是要占用空间,那么如果不及时回收就会对系统的运行产生影响。java和c 一个很大的区别就在于,java的垃圾回收主要是jvm去做,而c语言是自己去控制。虽然JAVA可以手动的调用方法 system.gc 去手动控制垃圾回收,但据说达不到立马回收的效果。c 语言则是要自己去申请一块内存空间malloc ,使用完成还需要手动去释放掉,如果没有及时释放,或者申请出现内存过大等,会造成内存溢出等异常,不过功底深厚的大牛都会做的比较牛逼,很好的去控制。

2、如何判断对象生死?

在内存回收的过程中,首先要确定一点,该对象是否应该被回收,哪些还"存活",哪些已"死亡"。

  • 引用计数法

当一个对象在代码中被引用,那么就会在这个对象的引用计数值 + 1,当引用失效的时候,计数值则相应 - 1。但是如果两个对象存在相互引用的情况,如下:

A a = new A();
B b = new B();
a.instance = b;
b.instance = a;

虚拟机就无法去判断这两个对象是否需要被回收,因为彼此的引用值都不是 0。就是说引用计数法很难解决对象之间的相互引用问题, 也无法通知GC回收器去及时回收它。因为存在这种缺点,所以现在的虚拟机基本上并不是通过引用计数法来判断对象是否存活。那是通过什么方法? 请看官往下看。

  • 可达性分析算法

现在主流的商用语言的视线中都是通过可达性分析来判断对象是否存活,比如JAVA,C#等。这种方法基本思想 ——以 "GC Roots "(1.虚拟机栈中reference对象,2.方法区静态属性引用对象,3.方法区常量引用对象,4.本地方法栈 所谓的native方法 引用的对象)的对象作为起点向下搜索,搜索走过的路径被称为"引用链",当一个对象没有任何引用链相连,那么这个对象就是不可用的。如下图所示:

注:并不是不可达的对象就必须 "死",他们还是处于"缓刑", 真正要宣告一个对象死亡,需要经过两次标记的过程:经过可达性分析后对象没有和GC Roots 连接的引用链,那么需要被标记一次然后还需要经过筛选(筛选条件:判断该对象是否有必要执行finalize()方法),如果对象已经调用了或者没有覆盖finalize方法(finalize() 方法只会被执行一次!),那么 虚拟机判定该对象是 "没有必要执行该方法"。

如果该对象有必要执行finalize方法,那么对象会被放置在一个叫做F-Queue 的队列之中,之后会由虚拟机自动建立,由低优先级的Finalize 方法去执行它。(执行时只去触发对象的finalize()方法,但是并不等待他运行结束,防止有的对象finalize()进行缓慢,或者死循环,会导致队列持续等待,进而内存回收系统崩溃。)稍后GC 会对F-Queue 队列中的对象进行第二次标记,当finalize 方法执行后成功将对象连接到引用链上任何一个对象,那么这个对象就被拯救成功了,不然则go die!

  • 什么是引用?

Java中定义:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称是这块内存的一个引用。

        强引用:类似于A a = new A(),只要引用在,就永远不会回收被引用的对象。

        软引用:描述有用但并非必须的独享。在系统将要发生内存溢出的时候,会将这部分对象回收。    

        弱引用:非必需对象引用,能生存到下次发生GC之前。

        虚引用:一点用都没。只有当对象被垃圾回收器回收的时候会收到一个系统通知。

  • 方法区回收

在我看来方法区的内存,回收起来并没有新生代那么明显。方法区大多存有类的描述信息,静态变量,常量,方法等信息,这些大多是系统常用的,很少去回收,回收效率微乎其微。

然而永久带的方法回收主要分成两部分,一种是常量的回收,另一种是类的回收。

  • 常量的回收    相当于这个常量已经被废弃掉了。例如:方法区的常量池中有一个字符串常量 "java", 当系统中没有一个String对象指向这个常量的值得时候,那么这个常量在发生GC的时候将会被回收。
  • 类的回收       类的回收相对于常量的回收会麻烦多,需要满足下面三个条件才会被回收: 
    • 类中所有的对象都被回收,就是堆中不存在该类的任何的实例;
    • 加载该类的classloader被回收;
    • 该类对应的java.lang.Class对象没有在任何地方引用,或是通过反射机制访问不到该类;

注: 在大量使用反射,动态代理,cGLib等ByteCode框架、动态生成Jsp等频繁定义classLoader的场景都需要虚拟机具备类的卸载功能,防止永久带不会溢出。

3、垃圾回收算法

  • 标记-清除算法 

标记-清除 算法是最基础的算法,为什么呢?因为后面的要讲的算法很多是从这个基本的算法改变其不足演变而来。

标记-清除(Mark-Sweep) 算法正如其名字所说由两个部分来完成。首先,要对需要回收的对象进行标记,如何标记上面已经提过。然后,要对这些被标记的对象进行收集。

     标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。 
   清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

如下图所示:

感觉清理完成之后,内存零零散散,故该算法有以下两个缺点:

缺点一:被标记的对象在内存中分布很零散,回收之后可用内存很零碎。如果当一个进程需要申请一块连续的较大内存时,无法找到足够的连续内存,不得不提前触发一次垃圾回收的动作。

缺点二:标记和清除的过程效率不高,而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差,尤其对于交互式的应用程序来说简直是无法接受。

  • 复制算法

复制算法是对标记-清除算法在回收后出现很多内存碎片的一种改进,而且效率也有所提升。

复制算法(copying)将可用的内存容量划分成大小相等两块,每次只使用其中一块,当一块内存用完了,将还存活的对象复制到另一块内存中,然后将之前使用过的内存空间全部清理掉。当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程 ,它会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。 清空之前的内存块,减少了大量不连续的内存碎片的产生。

实现简单且运行高效。如下图所示:

细心的读者会发现,这种算法有个很大的缺点——将可使用的内存缩小成原来的一半,代价太大了!所以现在的虚拟机厂商都采用复制算法来回收新生代。研究表明 新生代对象98%都是 “朝生夕死” 所以不需要按照1:1划分内存空间,而是将内存分为一块较大的Eden 空间和两块较小的Survivor 空间。每次只是用Eden 和 其中一块 Survivor区域,另一块Survivor 则用来当作保留区。那么这样一来,每次进行回收的时候只需要将Eden 和 Survivor生还的对象复制到另一块 Survivor空间,然后清理。HotShot虚拟机 新生代划分比例默认 Eden:Survivor = 8:1。然而这样分配内存,有个问题。当作保留区的Survivor的内存大小不够承载 使用中的Eden和一块Survivor区域的存活对象怎么办?此时需要依赖其他内存(老年代)进行分配担保。

分配担保(Handle Promotion)——如果另一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象时,这些对象直接通过分配担保机制进入老年代。

发生Minor GC(只回收Eden Survivor区)前,虚拟机检查老年代最大可用连续空间是否大于新生代所有对象总空间。如果大于,那么Minor GC确保是安全的。如果不大于,则需要查看虚拟机HandlePromotionFailure参数设置,是否允许担保失败。若允许(true),会继续检查老年代最大连续可用空间是是否大于历次晋升到老年代的对象平均大小。如果大于,会尝试一次 Minor GC,尽管是有风险。(因为仅仅是历次晋升到老年代对象平均大小与老生代最大连续空间比较,如果内存小无法容纳,此时进行Minor GC 会清理原本存活的对象所以是冒险的,进而需要进行Full GC)如果小于或者Handle Promotion Failure不允许冒险,那么要进行一次Full GC。

  • 标记-整理算法

复制算法是需要将对象从从内存一个区域复制到另一个区域,当发现对象存活率很高的情况下,效率很低。而且在老生代的回收中,大多不采用复制算法,没有额外的空间进行分配担保。

标记-整理算法(Mark-Compact),过程和Mark-Sweep 方法过程一样,也需要对对象进行标记,不过后续步骤不是直接对可回收对象进行清理,算法分成两个部分。  

    标记:遍历GC Roots,然后将存活的对象标记。 
    整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。

标记-整理算法不仅可以弥补标记-清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。不过标记-整理算法,效率是唯一缺点。它需要对存活对象进行标记,然后要整理存活对象内存地址,相对于复制算法效率较低。

  • 分代收集算法

分代收集算法,当前商用的虚拟机的垃圾收集大都采用这种算法。也不算是算法,只是把java 堆分成 新生代,老生代。分代之后,不同区域可以使用不同的收集算法。比如:

新生代 每次垃圾回收都会有大批对象死去,只有少量存活,那就采用复制-算法

老生代 对象存活率高,也没额外的空间对它进行分配担保使用标记-整理/清除法会更好来回收。不同场景使用不同的算法更加有利于整体效率的提升。

        JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。因此GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC),它们所针对的区域如下。 
  普通GC(minor GC):只针对新生代区域的GC。 
  全局GC(major GC or Full GC):针对年老代的GC,偶尔伴随对新生代的GC以及对永久代的GC。 
  由于年老代与永久代相对来说GC效果不好,而且二者的内存使用增长速度也慢,因此一般情况下,需要经过好几次普通GC,才会触发一次全局GC。

转载于:https://my.oschina.net/xiaomianyang/blog/737666

JVM 对象引用标记 与 内存回收算法相关推荐

  1. 【JVM学习笔记】内存回收与内存回收算法 就哪些地方需要回收、什么时候回收、如何回收三个问题进行分析和说明

    目录 一.相关名词解释 垃圾收集常用名词 二.哪些地方需要回收 本地方法栈.虚拟机栈.程序计数器 方法区 Java堆 三.什么时候回收 1. 内存能否被回收 内存中的引用类型 引用计数算法 可达性分析 ...

  2. 内存回收算法与 Hot Spot 算法实现细节

    文章目录 内存回收算法概述 对象存活判定算法 引用计数算法 可达性分析算法 垃圾收集算法 分代收集理论 标记-清除算法 标记-复制算法 半区复制算法 Appel 式复制算法 Appel 式复制算法的逃 ...

  3. 直通BAT必考题系列:JVM的4种垃圾回收算法、垃圾回收机制与总结

    BAT必考JVM系列专题 直通BAT必考题系列:深入详解JVM内存模型与JVM参数详细配置  垃圾回收算法 1.标记清除 标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段. 在标记阶段首先通过 ...

  4. Java虚拟机JVM常用的几种回收算法和垃圾回收器

    1. 垃圾回收算法 1.1 "标记-清除"算法 对内存对象进行可达性分析并标记,标记完成后统一回收.书中说有两个不足:①标记和清除效率不高:②内存空间是大量不连续的内存碎片.标记是 ...

  5. JVM内存回收算法简述

    2019独角兽企业重金招聘Python工程师标准>>> 在第一代面向对象语言C++中,最让人头疼以及影响敏捷开发的无疑是内存的申请与回收 在程序运行时,使用享元设计使用的一些代码复用 ...

  6. 小师妹学JVM之:GC的垃圾回收算法

    文章目录 简介 对象的生命周期 垃圾回收算法 Mark and sweep Concurrent mark sweep (CMS) Serial garbage collection Parallel ...

  7. 根可达算法的根_好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析

    0x01 什么是垃圾 很简单,没有引用指向的任何对象都叫做垃圾(garbage). 什么是garbage 在某一内存空间中,Java程序制造了很多对象被引用,有的对象还引用别的对象,中途有对象不被需要 ...

  8. 面试篇之JVM(GC 可达性分析 回收算法 卡表 G1)

    面试篇之JVM 面试篇之JVM GC 对象存活判断 分代回收理论 常见回收算法 HotSpot实现细节 记忆集与卡表 读写屏障 回收器 并发标记 G1 面试篇之JVM GC 美团 1.JVM怎么判断一 ...

  9. JVM底层原理+四大垃圾回收算法详解-周阳老师

    转载自,感谢原作者:https://www.jianshu.com/p/9e6841a895b4 注意:垃圾回收算法周阳老师讲的有错误,具体在p19,四大垃圾回收算法为复制算法.标记-整理算法.标记- ...

最新文章

  1. elasticsearch安装与配置介绍
  2. mysql group by日期_深入研究mysql中group by与order by取分类最新时间内容
  3. 【C++】构建栈 进栈和出栈
  4. mysql 允许其他主机访问权限_允许其他主机访问本机MySQL
  5. 2011年数据库大会纪行
  6. [Hadoop in China 2011] 中兴:NoSQL应用现状及电信业务实践
  7. 微软project服务器搭建,Project Professional 版本(Project Server 2010 设置)
  8. Android立刻终止一个线程
  9. JB的Python之旅-爬虫篇--requestsScrapy
  10. 阴阳师哪个服务器可以用微信登录,网易大神怎么绑定阴阳师QQ微信账号 网易大神绑定阴阳师QQ微信账号方法...
  11. 程序集定义(Assembly Definition File)
  12. WEB工程师和设计师必学的10个IOS 8新鲜改变
  13. Arduino整型字符串互转
  14. realsense中IMU的简单使用与学习
  15. eclispe启动报错
  16. 性能服务器石墨和化合物区别,南开大学陈军教授Nano Lett. :具有高容量和快速钠离子存储性能的硫化锑-石墨烯化合物...
  17. 全民一起VBA实战篇 专题3 第一回 格式化规定各人迥异,Format函数一招摆平
  18. 计算机中64位和32位什么意思?有什么区别?
  19. 各种sharebutton 的使用
  20. JAVA怎么替换html标签呢???

热门文章

  1. maven 排除某个类_java-如何从Maven依赖项中排除某些程序包(在JAR中)?
  2. 在RedHat Linux系统中安装和配置snmp服务
  3. Tuxedo FAQs 整理
  4. C++ 推断进程是否存在
  5. Linux中如何通过命令行访问Dropbox
  6. eventlog analyzer 8.0
  7. 循环 计算数值的整数次幂
  8. Linux 命令(120)—— route 命令
  9. Golang sync.Once 简介与用法
  10. 设计模式(3)——装饰者模式(Decorator,结构型)