垃圾回收

      程序计数器、虚拟机栈、本地方法栈3个区域随线程而生随线程而灭,这几个区域不需要过多考虑回收的问题,在方法结束或线程结束的时内存就跟着回收了。

垃圾回收主要关注的区域是java的堆和方法区。

引用计数算法

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

但主流的java虚拟机里面没有选用引用计数法来管理内存,其主要原因时它很难解决相互循环引用的问题

可达性分析算法

 在主流的商用程序语言(Java、C,甚至包括前面提到的古老的Lisp)的主流实现中都是称通过可达性分析( Reachabilityanalysis)来判定对象是否存活的,这个算法的基木路就是通过一系列的称为“GCRo”的对象作为起始点,从这些节点开始向下搜索,所走过的路径称为引用链( Reference Chain),当一个对象到 GC Roots 没有任何引用链相用图论的话来说,就是从 GC Roots到这个对象不可达)时,则证明此对象是不可用的。

在Java语言中,GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性实体引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象。即GC root包括栈中引用对象和方法区中引用对象。

引用

强引用指在程序代码之中普遍存在的,类似“ Object obj=new Objec°这类的引用,只要强引用还存在,垃圾收集器水远不会回收掉被引用的对象

软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在DK12之后,提供了Softreference类来实现软引用。

弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了Weak Reference类来实现弱引用。

虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时数到个系统通知。在JDK1.2之后,提供了 Phantomreference 2类来实现虚引用。

要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与 GC Roots相连接的引用链,那它将会被第一次标记并且进行一次第选,筛选的条件是此对象是否有必要执行 finalize方法(当对象没有覆盖 finalize()方法,或者 finalize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”)。

 

对象的生存还是死亡?

如果这个对象被判定为有必要执行 finalize方法,那么这个对象将会放置在一个叫F- Queue的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer f线程去执行它,这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样做的原因是,如果一个对象在 finalize0方法中执行缓慢,或者发生了死循环(更极端的情况),将很可能会导致F- Queue队列中其他对象水久处于等待,甚至导致整个内存回收系统崩溃。 finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F- Queue中的对象进行第二次小规模的标记,如果对象要在 finalize()中成功拯救自己一一只要重新与引用链上的任何一个对象建立关联即可,如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合:如果对象这时候还没有逃脱,那基本上它就真的被回收了。

Jvm垃圾回收器来看,存储空间中每个对象都可能处于以下三个状态之一:

可触及状态:当一个对象被创建后,还有引用变量在引用它

可复活状态:当对象不被任何引用变量引用时,会调用finalize 方法,这些finalize方法可能使对象重新转到可触及状态

不可触及状态:当ivm执行完所有可复活对象的finalize方法,这些方法都没有使对象转到可触及状态,对象就会进入不可触及状态,只有这个状态时,垃圾回收器才会回收它占用的空间。 

方法区的垃圾回收?

很多人认为方法区(或者 Hotspot虚拟机中的水久代)是没有垃圾收集的,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集,而且在方法区中进行垃圾收集的“性价比”一般比较低:在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70%~95%的空间,而永久代的垃圾收集效率远低于此。永久代的垃圾收集主要回收两部分内容废弃常量无用的类

废弃常量

回收废弃常量与回收Java堆中的对象非常类似。如果没有任何 String对象引用常量池中的“abe”常量,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个“abe”常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

“无用的类”

判定一个类是否是“无用的类”同时满足下面3个条件才能算是“无用的类":

口该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。

口加载该类的 Classloader已经被回收。

口该类对应的 java. lang Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

垃圾回收算法

标记-清除算法

 算法分为”标记“和”清除“两个阶段,即:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

  不足:一,效率问题,标记和清除两个过程的效率都不高;二,空间问题,标记清除后会产生大量不连续的内存碎片。

复制算法

 将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

不足:可使用的内存降为原来一半。

标记-整理算法

标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理

标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。

复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。

分代收集算法

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

现在的Java虚拟机就联合使用了分代复制、标记-清除和标记-整理算法。

堆内存被分成新生代和年老代两个部分,整个堆内存使用分代复制垃圾收集算法。

jvm部分结构使用垃圾收集算法情况

(1).新生代:

新生代使用复制和标记-清除垃圾收集算法,研究表明,新生代中98%的对象是朝生夕死的短生命周期对象,所以不需要将新生代划分为容量大小相等的两部分内存,而是将新生代分为Eden区,Survivor from和Survivor to三部分,其占新生代内存容量默认比例分别为8:1:1,其中Survivor from和Survivor to总有一个区域是空白,只有Eden和其中一个Survivor总共90%的新生代容量用于为新创建的对象分配内存,只有10%的Survivor内存浪费,当新生代内存空间不足需要进行垃圾回收时,仍然存活的对象被复制到空白的Survivor内存区域中,Eden和非空白的Survivor进行标记-清理回收,两个Survivor区域是轮换的

新生代中98%情况下空白Survivor都可以存放垃圾回收时仍然存活的对象,2%的极端情况下,如果空白Survivor空间无法存放下仍然存活的对象时,使用内存分配担保机制,直接将新生代依然存活的对象复制到年老代内存中,同时对于创建大对象时,如果新生代中无足够的连续内存时,也直接在年老代中分配内存空间。

Java虚拟机对新生代的垃圾回收称为Minor GC,次数比较频繁,每次回收时间也比较短。

使用java虚拟机-Xmn参数可以指定新生代内存大小。

(2).年老代:

年老代中的对象一般都是长生命周期对象,对象的存活率比较高,因此在年老代中使用标记-整理垃圾回收算法。

Java虚拟机对年老代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。

当新生代中无足够空间为对象创建分配内存,年老代中内存回收也无法回收到足够的内存空间,并且新生代和年老代空间无法在扩展时,堆就会产生OutOfMemoryError异常。

java虚拟机-Xms参数可以指定最小内存大小,-Xmx参数可以指定最大内存大小,这两个参数分别减去Xmn参数指定的新生代内存大小,可以计算出年老代最小和最大内存容量。

(3).永久代:

java虚拟机内存中的方法区在Sun HotSpot虚拟机中被称为永久代,是被各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。永久代垃圾回收比较少,效率也比较低,但是也必须进行垃圾回收,否则会永久代内存不够用时仍然会抛出OutOfMemoryError异常。

永久代也使用标记-整理算法进行垃圾回收,java虚拟机参数-XX:PermSize和-XX:MaxPermSize可以设置永久代的初始大小和最大容量。

什么情况下引起GC

minor GC :指发生在新生代的垃圾收集动作,当eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.

major GC/ Full GC:指发生在老年代的GC,majorGC 的速度一般会比minor GC 慢10倍以上。

①手动调用System.gc()方法 [增加了full GC频率,不建议使用而是让jvm自己管理内存,可以设置-XX:+ DisableExplicitGC来禁止RMI调用System.gc]

②发现perm gen(如果存在永久代的话)需分配空间但已经没有足够空间

③老年代空间不足,比如说新生代的大对象大数组晋升到老年代就可能导致老年代空间不足。

④CMS GC时出现Promotion Faield[pf]

⑤统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间。

这个比较难理解,这是HotSpot为了避免由于新生代晋升到老年代导致老年代空间不足而触发的FUll GC。

比如程序第一次触发Minor GC后,有5m的对象晋升到老年代,姑且现在平均算5m,那么下次Minor GC发生时,先判断现在老年代剩余空间大小是否超过5m,如果小于5m,则HotSpot则会触发full GC

 GC新生代对象晋升到老年代情况总结

(1)、Eden区满时,进行Minor GC,当Eden和一个Survivor区中依然存活的对象无法放入到Survivor中,则通过分配担保机制提前转移到老年代中。 

(2)、若对象体积太大, 新生代无法容纳这个对象,-XX:PretenureSizeThreshold即对象的大小大于此值, 就会绕过新生代, 直接在老年代分配, 此参数只对Serial及ParNew两款收集器有效。

(3)、长期存活的对象将进入老年代。

        虚拟机对每个对象定义了一个对象年龄(Age)计数器。当年龄增加到一定的临界值时,就会晋升到老年代中,该临界值由参数:-XX:MaxTenuringThreshold来设置。

        如果对象在Eden出生并在第一次发生MinorGC时仍然存活,并且能够被Survivor中所容纳的话,则该对象会被移动到Survivor中,并且设Age=1;以后每经历一次Minor GC,该对象还存活的话Age=Age+1。

(4)、动态对象年龄判定。

        虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同年龄(设年龄为age)的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄(age)的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

转:https://blog.csdn.net/qq_27327855/article/details/80586510

 


 

参考:https://www.cnblogs.com/huajiezh/p/5769255.html

          https://blog.csdn.net/wangxiaotongfan/article/details/82389881

posted @ 2019-02-18 17:59  Nausicaa0505  阅读(174)  评论(0编辑  收藏  举报