• 面试题(GC)


    参考https://blog.csdn.net/m0_38110132/article/details/74542143

    参考https://blog.csdn.net/CrankZ/article/details/86009279#

    参考https://blog.csdn.net/CrankZ/article/details/86009279#

    6.详谈一下Java内存模型以及GC算法:

    (1). jvm结构

    JVM的内部体系结构分为三部分
     (1)类装载器(ClassLoader)子系统
        作用: 用来装载.class文件
     (2)执行引擎
        作用:执行字节码,或者执行本地方法
     (3)运行时数据区
        方法区,堆,java栈,PC寄存器,本地方法栈

    JVM类加载简介:

     JVM将整个类加载过程划分为了三个步骤:

    (1)装载
      装载过程负责找到二进制字节码并加载至JVM中。JVM通过类名、类所在的包名通过ClassLoader来完成类的加载,同样,也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID。
    (2)链接
      链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。在完成了校验后,JVM初始化类中的静态变量,并将其值赋为默认值。最后一步为对类中的所有属性、方法进行验证,以确保其需要调用的属性、方法存在,以及具备应的权限(例如public、private域权限等),会造成NoSuchMethodError、NoSuchFieldError等错误信息。
    (3)初始化
      初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化。在四种情况下初始化过程会被触发执行:调用了new;反射调用了类中的方法;子类调用了初始化;JVM启动过程中指定的初始化类。

    以下主要介绍运行时数据区。

    Java虚拟机运行时数据区域被分为五个区域:堆(Heap)、栈(Stack)、本地方法栈(Native Stack)、方法区(MethodArea)、程序计数器(Program Count Register)。

    1.1 程序计数器

    程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

    分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

    由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

    如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。

    1.2 虚拟机栈(java栈)

    线程私有,它的生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。

    动画是由一帧一帧图片连续切换结果的结果而产生的,其实虚拟机的运行和动画也类似,每个在虚拟机中运行的程序也是由许多的帧的切换产生的结果,只是这些帧里面存放的是方法的局部变量,操作数栈,动态链接,方法返回地址和一些额外的附加信息组成。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

    对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。

    1.3 本地方法栈

    本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。

    与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

    1.4 方法区

    方法区在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来的。类(静态)变量也存储在方法区中。

    简单说方法区用来存储类型的元数据信息,一个.class文件是类被java虚拟机使用之前的表现形式,一旦这个类要被使用,java虚拟机就会对其进行装载、连接(验证、准备、解析)和初始化。而装载(后的结果就是由.class文件转变为方法区中的一段特定的数据结构。这个数据结构会存储如下信息:

    1.4.1 类型信息

          这个类型的全限定名  这个类型的直接超类的全限定   这个类型是类类型还是接口类型    这个类型的访问修饰符    任何直接超接口的全限定名的有序列表

    1.4.2 字段信息    字段名  字段类型    字段的修饰符

    1.4.3 方法信息   方法名  方法返回类型   方法参数的数量和类型(按照顺序)   方法的修饰符

    1.4.4 其他信息

    方法区主要有以下几个特点:

    1、方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待

    2、方法区的大小不必是固定的,JVM可根据应用需要动态调整。同时,方法区也不一定是连续的,方法区可以在一个堆(甚至是JVM自己的堆)中自由分配。

    3、方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集

    1.5 堆

    堆是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。但是随着JIT 编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。

    堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”。

    总结

    名称

    特征

    作用

    配置参数

    异常

    程序计数器

    占用内存小,线程私有,

    生命周期与线程相同

    大致为字节码行号指示器

    虚拟机栈

    线程私有,生命周期与线程相同,使用连续的内存空间

    Java 方法执行的内存模型,存储局部变量表、操作栈、动态链接、方法出口等信息

    -Xss

    StackOverflowError

    OutOfMemoryError

    java堆

    线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址

    保存对象实例,所有对象实例(包括数组)都要在堆上分配

    -Xms

    -Xsx

    -Xmn

    OutOfMemoryError

    方法区

    线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址

    存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

    -XX:PermSize:

    16M

    -XX:MaxPermSize

    64M

    OutOfMemoryError

    运行时常量池

    方法区的一部分,具有动态性

    存放字面量及符号引用

    2.GC机制

    垃圾收集器一般必须完成两件事:检测出垃圾;回收垃圾。怎么检测出垃圾?一般有以下几种方法:

    2.1 引用计数法

    给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1。

    好了,问题来了,如果我有两个对象A和B,互相引用,除此之外,没有其他任何对象引用它们,实际上这两个对象已经无法访问,即是我们说的垃圾对象。但是互相引用,计数不为0,导致无法回收,所以还有另一种方法:

    2.2 可达性分析算法

    以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。这里的根集一般包括java栈中引用的对象、方法区常量池中引用的对象、本地方法中引用的对象等。

    总之,JVM在做垃圾回收的时候,会检查堆中的所有对象是否会被这些根集对象引用,不能够被引用的对象就会被垃圾收集器回收。

    2.3 一般回收算法也有如下几种

    2.3.1 按照基本回收策略分

    (1)标记-清除(Mark-sweep)

    算法和名字一样,分为两个阶段:标记和清除。标记所有需要回收的对象,然后统一回收。这是最基础的算法,后续的收集算法都是基于这个算法扩展的。

    不足:效率低;标记清除之后会产生大量碎片。效果图如下:

    (2)复制(Copying)

    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。效果图如下:

    (3)标记-整理(Mark-Compact)

    此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。效果图如下:

    2.3.2 按分区对待的方式分

    (1)增量收集(Incremental Collecting):实时垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。

    (2)分代收集(Generational Collecting):基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

    2.7.2 虚拟机的GC过程

    经过上面介绍,我们已经知道了JVM为何要分代回收,下面我们就详细看一下整个回收过程。

    1. 在初始阶段,新创建的对象被分配到Eden区,survivor的两块空间都为空。 
      这里写图片描述

    2. 当Eden区满了的时候,minor garbage 被触发 
      这里写图片描述

    3. 经过扫描与标记,存活的对象被复制到S0,不存活的对象被回收 
      这里写图片描述

    4. 在下一次的Minor GC中,Eden区的情况和上面一致,没有引用的对象被回收,存活的对象被复制到survivor区。然而在survivor区,S0的所有的数据都被复制到S1,需要注意的是,在上次minor GC过程中移动到S0中的两个对象在复制到S1后其年龄要加1。此时Eden区S0区被清空,所有存活的数据都复制到了S1区,并且S1区存在着年龄不一样的对象,过程如下图所示: 
      这里写图片描述

    5. 再下一次MinorGC则重复这个过程,这一次survivor的两个区对换,存活的对象被复制到S0,存活的对象年龄加1,Eden区和另一个survivor区被清空。 
      这里写图片描述

    6. 下面演示一下Promotion过程,再经过几次Minor GC之后,当存活对象的年龄达到一个阈值之后(可通过参数配置,默认是8),就会被从年轻代Promotion到老年代。 
      这里写图片描述

    7. 随着MinorGC一次又一次的进行,不断会有新的对象被promote到老年代。 
      这里写图片描述

    8. 上面基本上覆盖了整个年轻代所有的回收过程。最终,MajorGC将会在老年代发生,老年代的空间将会被清除和压缩。 
      这里写图片描述

    从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方式分配内存和清理内存的效率都极高,这种垃圾回收的方式就是著名的“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中),这不代表着停止复制清理法很高效,其实,它也只在这种情况下(基于大部分对象存活周期很短的事实)高效,如果在老年代采用停止复制,则是非常不合适的。

    老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。一般,老年代用的算法是标记-压缩算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。

    关于方法区即永久代的回收,永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点 

    1. 类的所有实例都已经被回收

     2. 加载类的ClassLoader已经被回收

    3. 类对象的Class对象没有被引用(即没有通过反射引用该类的地方)

    永久代是用于存放静态文件,如Java类、方法等。 
    持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class。

    永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。

    附:

    链接:https://www.nowcoder.com/questionTerminal/970cdaaa4a114cbf9fef82213a7dabca
    来源:牛客网

    HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

    因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

    在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。


    常见配置汇总

      1. 堆设置
        • -Xms:初始堆大小
        • -Xmx:最大堆大小
        • -XX:NewSize=n:设置年轻代大小
        • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
        • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
        • -XX:MaxPermSize=n:设置持久代大小
      2. 收集器设置
        • -XX:+UseSerialGC:设置串行收集器
        • -XX:+UseParallelGC:设置并行收集器
        • -XX:+UseParalledlOldGC:设置并行年老代收集器
        • -XX:+UseConcMarkSweepGC:设置并发收集器
      3. 垃圾回收统计信息
        • -XX:+PrintGC
        • -XX:+PrintGCDetails
        • -XX:+PrintGCTimeStamps
        • -Xloggc:filename
      4. 并行收集器设置
        • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
        • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
        • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
      5. 并发收集器设置
        • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
        • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

    JVM什么情况下会触发Full GC(Major GC)

    除直接调用System.gc外,触发Full GC执行的情况有如下四种。
    1. 旧生代空间不足
    旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
    java.lang.OutOfMemoryError: Java heap space 
    为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
    2. Permanet Generation(永久代)空间满
    PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
    java.lang.OutOfMemoryError: PermGen space 
    为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
    3. CMS GC时出现promotion failed(担保:s1s2--old)和concurrent mode failure
    对于采用CMS进行旧生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。
    promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。
    应对措施为:增大survivorspace、旧生代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。
    4. 统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
    这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。
    例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。
    当新生代采用PSGC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。
    除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- java-Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。


    图中展示了7种不同分代的收集器:

    Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;

    而它们所处区域,则表明其是属于新生代收集器还是老年代收集器:

    新生代收集器:Serial、ParNew、Parallel Scavenge;

    老年代收集器:Serial Old、Parallel Old、CMS;

    整堆收集器:G1;

    两个收集器间有连线,表明它们可以搭配使用:

    Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1;

    Serial收集器


    Serial收集器是单线程收集器,是分代收集器。它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop TheWorld)。

    新生代:单线程复制收集算法;
    老年代:单线程标记整理算法。
    Serial一般在单核的机器上使用,是Java 5非服务端JVM的默认收集器,参数-XX:UseSerialGC设置使用。
    优势:对于单CPU环境来说,Serial收集器没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集。Serial收集器对于在Client模式下的虚拟机是一个很好的选择。

    ParNew收集器

    ParNew/Serial Old组合收集器运行示意图如下:


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

    参数控制:
    -XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;
    -XX:+UseParNewGC":强制指定使用ParNew;
    -XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;

    优势:ParNew收集器是许多运行在server模式下的虚拟机中首选的新生代收集器,一个重要的原因是,只有ParNew和Serial收集器能和CMS收集器共同工作。无法与JDK1.4中存在的新生代收集器Parallel Scavenge配合工作,所以在JDK1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew和Serial。

    ParNew收集器在单CPU环境中不比Serial效果好,甚至可能更差,两个CPU也不一定跑的过,但随着CPU数量的增加,性能会逐步增加。默认开启的收集线程数与CPU数量相同。在CPU数量很多的情况下,可以使用-XX:ParallelGCThreads参数来限制线程数。

    Parallel Scavenge收集器

    Parallel Scavenge收集器是一个新生代的手机器,使用的是复制算法的收集器,而且也是多线程的收集器。
    Parallel Scavenge收集器,目标达到一个可控制的吞吐量,使用-XX:MaxGCPauseMillus参数控制垃圾停顿时间,使用-XX:GCTimeRatio参数控制吞吐量。Parallel Scavenge收集器设置-XX:UseAdaptiveSizePolicy参数,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大吞吐量(GC自使用的调节策略)。
    自适应调节策略也是Parallel Scavenge收集器和ParNew收集器一个重要的区别。

    Serial Old收集器

    Serial收集器的老年代版,它同样是一个单线程收集器,使用标记–整理算法。收集器的意义在于给Client模式下的虚拟机使用。如果在Server模式下,那么它主要有两大用途:一种是在jdk1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后预案,在并发收集发生Concurrent Mode Failure时使用。工作流程图如下:


    Parallel Old 收集器

    Parallel Scavenge收集器的老年代版,使用多线程与标记–整理算法。这个收集器在jdk1.6中才开始提供的,直到Parallel Old 收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加 Parallel Old收集器


    CMS收集器

    一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。CMS收集器是基于“标记-清除”算法实现的,主要分为4个步骤。

    初始标记(CMS inital mark):需要“stop the world”,但只标记一下GC Roots能直接关联的对象,速度很快。
    并发标记(CMS concurrent mark):是GC Roots Tracing的过程,花费时间长
    重新标记(CMS remark):*需要“stop the world”,是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
    并发清除(CMS concurrent sweep):是并发清除无用对象。
    缺点:

    CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是因为占用了一部分CPU资源而导致应用程序变慢,总吞吐量就会降低。CMS默认启动的回收线程数为(CPU数量+3)/4。当CPU的个数少于2个的时候,CMS对用户程序的影响可能会变得很大。
    CMS收集器无法处理浮动垃圾(floating garbage),可能会出现concurrent mode failure导致另一次full gc的产生。在CMS的并发清理阶段,由于程序还在运行,垃圾还会不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理掉它们,只好留到下一次GC再处理。这种垃圾称为浮动垃圾。同样由于CMS GC阶段用户线程还需要运行,即还需要预留足够的内存空间供用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被灌满了再进行收集而需要预留一部分空间提供并发收集时的程序运作使用。默认设置下 CMS收集器在老年代使用了68%的空间后就会被激活。这个值可以用-XX:CMSInitiatingOccupancyFraction来设置。要是CMS运行期间预留的内存无法满足程序需要,就会出现concurrent mode failure,这时候就会启用Serial Old收集器作为备用进行老年代的垃圾收集。
    空间碎片过多(标记-清除算法的弊端),CMS是基于标记-清除算法来实现的回收器,提供-XX:+UseCMSCompactAtFullCollection参数,应用于在FULL GC后再进行一个碎片整理过程。-XX:CMSFullGCsBeforeCompaction,多少次不压缩的full gc后来一次带压缩的。
    G1收集器

    G1收集器(Garbage-First):是当今收集器技术发展的最前沿的成果之一,G1是一款面向服务器端应用的垃圾收集器。 使用G1收集器时,java堆的内存布局就与其他收集器有很大差别,它将真个java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代与老年代的概念,但新生代与老年代不再试物理隔离的了,他们都是一部分Region(不需要连续)的集合。G1具备如下特点:

    并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
    分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能够独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
    空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记–整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运行期间不会产生内存空间碎片,收集后能提供规整的可用内存。这个特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前出发下一次GC。
    可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时java(RTSJ)的垃圾收集器的特性了。
    初始标记(Initial Marking):标记GC Roots能够直接关联到的对象,并且修改TAMS的值,能在正确可用的Region中创建对象,这阶段需要停顿线程,而且耗时很短。
    并发标记(Concurrent Marking):从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这个时间耗时比较长,但可与用户程序并行执行。
    最终标记(Final Marking):为了修正和正在并发标记期间因用户程序继续运行而导致标记产生变动的那一部分没有标记记录,虚拟机将这一段对象变法记录在线程Rememberred Set logs里面,最终标记阶段需要把Remembered Set logs 的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并发执行。
    筛选回收(Live Data Counting and Evacuation):对各个Region的回收截止和成本进行排序,根据用户期望的GC停顿时间来制定回收计划,这阶段可以做到和用户程序一起并发执行,但是因为值回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅度提高手机效率。

    Minor GC和Full GC的区别

    (A)、Minor GC
    又称新生代GC,指发生在新生代的垃圾收集动作;
    因为Java对象大多是朝生夕灭,所以Minor GC非常频繁,一般回收速度也比较快;
    (B)、Full GC
    又称Major GC或老年代GC,指发生在老年代的GC;
    出现Full GC经常会伴随至少一次的Minor GC(不是绝对,Parallel Sacvenge收集器就可以选择设置Major GC策略);
    Major GC速度一般比Minor GC慢10倍以上;

    转自https://blog.csdn.net/u012998254/article/details/81635902

  • 相关阅读:
    Delphi 与 DirectX 之 DelphiX(10): TPictureCollectionItem.StretchDraw 绘制到指定矩形
    Delphi 与 DirectX 之 DelphiX(11): TPictureCollectionItem.DrawAdd、DrawSub
    Delphi 与 DirectX 之 DelphiX(13): TPictureCollectionItem.DrawRotate
    Delphi 与 DirectX 之 DelphiX(8): 第一个简单动画
    上周热点回顾(10.2210.28)
    如果云计算
    网站已恢复正常,让大家久等了
    上周热点回顾(10.1510.21)
    10.24,今天是程序员节,祝大家节日快乐
    园豆兑换阿里云代金券 体验阿里云云服务器
  • 原文地址:https://www.cnblogs.com/ffaiss/p/10598918.html
Copyright © 2020-2023  润新知