• 垃圾回收


          程序计数器、虚拟机栈、本地方法栈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


     垃圾收集器

    Serial收集器
    串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集的过程中会Stop The World(服务暂停)

    参数控制:-XX:+UseSerialGC 串行收集器

    ParNew收集器

    ParNew收集器其实就是Serial收集器的多线程版本。新生代并行,老年代串行;新生代复制算法、老年代标记-压缩

    参数控制:-XX:+UseParNewGC ParNew收集器  、 -XX:ParallelGCThreads 限制线程数量参数

    Parallel收集器

    Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩

    参数控制:-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行

    Parallel Old 收集器

    Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供

    参数控制:-XX:+UseParallelOldGC使用Parallel收集器+ 老年代并行

    CMS 收集器

    CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

    从名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为4个步骤,包括:

    初始标记(CMS initial mark)
    并发标记(CMS concurrent mark)
    重新标记(CMS remark)
    并发清除(CMS concurrent sweep)

    初始标记-短暂,仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
    并发标记-和用户的应用程序同时进行,进行GC RootsTracing的过程
    重新标记-短暂,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
    并发清除

    ​ 由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。

    -XX:+UseConcMarkSweepGC ,表示新生代使用ParNew,老年代的用CMS

    优点:并发收集、低停顿

    缺点:CMS收集器对CPU资源非常敏感

              无法处理浮动垃圾,可能出现“concurrent Mode Failure”失败而导致另一次Full GC的产生。

              可能会产生大量空间碎片

    参数控制:

    -XX:+UseConcMarkSweepGC 使用CMS收集器

    **-XX:+ UseCMSCompactAtFullCollection **Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长

    -XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理

    -XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)

    G1 收集器

    G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:

    1.空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
    2.可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
    上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。

    G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿。

    问题:把java堆分为多个Region后,垃圾收集是否就真的能以Region为单位进行?

    Region不可能是孤立的,一个对象分配再某个Region中,它并非只能被本Region中的其他对象引用,而是可以与整个java堆任意的对象发生引用关系。那在做可达性判定对象是否存活的时候,岂不是还得扫描整个java堆才能保证准确性?

    。。。。

    收集步骤:

    1、标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)

    2、Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。

    3、Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
    4、Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。

    5、Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。

     。。。。

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

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

  • 相关阅读:
    VRChat之blender教程
    29(30).socket网络基础
    26(27).反射及面向对象进阶
    25.python之面向对象
    24.configparser&hashlib
    23.logging
    22.re(正则表达式)
    22.XML
    java日志系统 @Slf4j注解的正确使用
    java四种元注解:@Retention @Target @Document @Inherited)认知
  • 原文地址:https://www.cnblogs.com/dingpeng9055/p/10395955.html
Copyright © 2020-2023  润新知