美文网首页SpringBoot极简教程 · Spring Boot Spring-BootSpring Boot
深入理解java虚拟机(二)垃圾收集器与内存分配策略

深入理解java虚拟机(二)垃圾收集器与内存分配策略

作者: z七夜 | 来源:发表于2018-07-25 17:58 被阅读2次

    写在前面:为了更加深入的了解java虚拟机,就看了一下《深入理解java虚拟机》这本书,一方面为了总结一下自己的认识,另一方面就是想与各位分享,如果有什么不对的地方,欢迎指正

    深入理解java虚拟机(一)java内存区域与内存溢出异常

    垃圾收集器与内存分配策略

    垃圾收集,三个步骤
    什么时候收集,收集那些,怎么收集

    1、收集那些

    我们会将一些不使用的对象进行收集,进行回收内存空间,我们怎么知道呢

    1、引用计数法

    如果这个实例被其他地方引用,那么计数器加一,如果解除引用那么就减一,当计数器为0说明没有地方使用,即可回收,但是缺点就是如果两个对象互相引用,但是后续又用不到,那么就不会被回收,内存浪费

    2、可达性分析

    通过GC Root向下寻找,形成一条引用链,如果对象不再这条链上,那么说明该对象不可达,可回收,

    什么是GC Root

    • 虚拟机栈中引用的对象
    • 方法区中静态属性引用的对象
    • 方法区常亮引用的对象
    • 本地方法栈中引用的对象

    3、引用

    我们不论是使用引用计数法,还是使用可达性分析,都有引用这个词,什么意思呢

    jdk1.2之前的引用,是reference类型的数据存储的是另一块内存的起始地址,那么就称这块内存代表着一个引用,这种情况下,一个对象只有被引用和没有被引用两种状态,

    我们希望有一些对象,在内存空间足够的时候,能够保留在内存中,如果内存空间在进行垃圾收集之后还是很紧张,那么就抛弃这些对象,所以出现 强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference),这4中引用强度依次减弱

    • 强引用
      强引用就是指程序代码中普遍存在的 Object obj = new Object()这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象,使用StrongReference实现
    • 软引用
      jdk1.2之后出现软引用用来描述一些有用,但不是必须的对象,软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列入可回收范围中进行第二次回收,如果这次回收还不能解决内存问题,那么抛出内存溢出错误,,使用SoftReference实现
    • 弱引用
      弱引用也是用来描述非必需的对象,被弱引用关联的对象,只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时候,无论当前内存是否足够,都会回收掉,使用WeakReference类实现虚引用
    • 虚引用
      最弱的引用,一个对象是否有徐勇勇的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,为一个对象设置虚引用关联的唯一目的,就是能在这个对象被收集器回收时收到一个系统通知,jdk1.2之后通过PhantomReference类来实现虚引用,

    4.对象死了吗

    当对象没有存在GC Root引用链上,也不是马上死,会被进行一次标记并进行一次筛选,查看对象的finalliaze方法是否被覆盖,或者是否被虚拟机调用过了,

    如果被覆盖或者没有被调用,那么这个对象会被放置到F-Queue中,等待执行finaliaze方法,虚拟机会开启一条低优先级的线程去触发这些方法,如果对象在finalize方法中重新加入了引用链(把自己赋值给某个类变量,或者对象的成员变量),那么就不会被回收了,

    5.回收方法区

    回收方法区内废弃常量和无用的类

    废弃常量:如果常量池中有各“abc”的字面量,但是却没有一个string 对象叫做abc,那么这个就是废弃常量,当垃圾回收的时候就会回收

    无用的类:在堆中已经没有这个类的实例,加载该类的类加载器已经被回收,这个Class对象没有在任何地方被引用,无法通过反射获取这个类的方法

    2、怎么收集

    垃圾收集在方法区和堆区

    1.标记-清除算法

    标记清除:当堆内存耗尽的时候,触发GC线程,使用户线程停止,遍历所有的GC root,将还存活的随想标记一下,之后清除所有未标记的对象,继续用户线程,缺点:产生内存碎片,使得内存不连续,当有对象申请大内存的时候,出现问题

    复制算法: 将原内存一分为二,只用一半的内存,当需要垃圾回收的时候, 标记存活对象,然后将存活对象拷贝到另一半空间中去,前一半直接全部回收,缺点:无论什么时候,只有一半的内存可用,浪费,拷贝对象需要时间

    标记整理算法:标记存活对象,将存活对象按照某种规则排列,清除别的对象,缺点:需要遍历标记,需要移动对象,浪费时间

    分代回收:
    堆分为新生代和老年代,新生代分三个模块,Enden surviver0 surviver1 8:1:1 new出来的对象放在enden区域,当发生gc,将存活的放在surviver0,第二次将存活的,surviver0中的放在surviver1中,反复如此,使用标记复制算法,当新生代的对象活过一定次数gc,就会去到老年代
    新生代使用复制算法,老年代使用标记清除,标记整理算法

    3、常见的垃圾收集器

    1.Serial垃圾收集器(客户端模式下的虚拟机,新生代默认收集器)

    单线程垃圾收集器,在进行垃圾收集的时候,会停止用户线程,当垃圾收集完毕,开始用户线程,这个是对用户不可见的,完全由虚拟机执行,单CPU效率最高的收集器,没有线程交替的开销

    2.ParNew垃圾收集器(服务端模式下的虚拟机,新生代默认收集器)

    Serial收集器的多线程版本,在进行垃圾收集,也会先停止用户线程,除了Serial只有这个收集器可以和CMS收集器使用,

    3. Parallel Scavenge收集器
    多线程并行的垃圾收集器,与ParNew类似,但是这个收集器注点在于达到一个可控制的吞吐量,吞吐量就是CPU用于运行用户代码的时间与cpu总消耗时间的比值,
    4. Serial Old收集器
    是serial收集器的老年版本,同样是一个单线程收集器,使用标记整理算法,是给客户端模式下的虚拟机使用,在server模式下的客户端有两大用途:一是在jdk1.5之前,与Parallel Scavenge收集器搭配使用,二就是作为CMS收集器的后备预案,在并发收集器发生Concurrent Mode Failure时使用,
    也会暂停用户线程
    5. CMS收集器
    是一种以获取短回收停顿时间为目标的收集器,采用标记清除方式,运作过程有

    • 初始标记
    • 并发标记
    • 重新标记
    • 并发清除

    其中初始标记与重新标记需要停止用户线程,初始标记只是标记一下GC Root能直接关联到的对象,速度很快,并发标记阶段就是进行GC Root Tracing的过程,而重新标记阶段则是为了修正并发标记期间因为用户程序继续运作而导致的标记产生变动的那一部分对象的记录,这个阶段停顿的时间一般会比初始标记阶段稍长一点,但远比并发标记的时间短,,由于整个过程中耗时最长的并发标记和并发清除过程,收集器线程可以和用户线程一起工作,所以,总体来看,CMS收集器的内存回收过程食欲用户线程一起并发执行的,
    缺点:CMS无法处理浮动垃圾,可能出现Concurrent Mode Failure失败而导致另一次Full GC的产生,由于CMS的并发清理阶段适合用户线程一起的,那么程序运行就会有新的垃圾产生,这一部分垃圾在标记之后,所以CMS不会处理,下一次处理, 也是由于在垃圾收集阶段用户线程还需要运行,那也就是需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全填满在进行收集,需要留一部分空间提供并发收集时的程序运作使用,当在CMS运行期间预留的内存无法满足程序需要就会出现一次Concurrent Mode Failure失败,这个时候就会启动Serial Old收集器进行老年代的垃圾收集,
    6. G1收集器
    具有以下特点,

    • 并行与并发
      充分利用多核多CPU,缩短Stop The World的时间,G1收集器通过并发的方式让java程序继续执行
    • 分代收集
      与其他收集器一样,分代概念也在G1中保存,G1不需要通过其他收集器配合就可以管理真个堆,但是它可以通过不同的方式处理,
    • 空间整合
      G1从整体来看是基于标记整理算法实现的,从局部来看是基于复制算法实现的,不会产生内存碎片,收集后能提供规整的可用内存,不会因为分配大对象找不到连续内存空间触发GC
    • 可预测的停顿

    G1可以让使用者明确指定在一个长度为n毫秒的时间片段内,消耗在垃圾收集上的时间不能超过n毫秒,
    具体过程
    G1将对分为多个大小相等的独立区域(Region),虽然保存着新生代与老年代的概念,但是不是物理隔离的,G1可以预测停顿时间,是因为,在进行垃圾收集的时候,G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小与回收所需要的时间的经验值),在后台维护一个列表,每次根据允许的收集时间,优先回收价值最大的Region,保证了G1收集器在有限时间内有高效率
    具体步骤:

    • 初始标记
    • 并发标记
    • 最终标记
    • 筛选回收

    初始标记阶段是标记一下GC root能够达到的对象,会停顿用户线程,但是时间很短,并发标记是从GC Root开始从堆中对象进行可达性分析,找出存活的对象,时间较长,但是可以和用户线程并发执行,最终标记阶段是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分,筛选回收,则是进行回收

    4、内存分配与回收策略

    Minor GC : 新生代的垃圾回收

    Full GC:老年代的垃圾回收,也会回收新生代

    1.对象优先在Eden分配

    大多数情况下,对象在新生代Eden区中分配,当Eden没有足够空间分配,就是触发Minor GC

    2.大对象直接进入老年代

    大对象,指需要大量连续内存空间的java对象,最典型的的就是那种很长的字符串和数组

    3.长期存活的对象进入老年代

    在Eden区的对象经过一次Minor GC,会被移动到Survivor区,年龄加一,当存活过一定时间,就会进入老年区

    4.动态对象年龄判定

    当survivor中相同年龄所有对象大小的总和大于survivor空间的一般,年龄大于等于改年龄的对象可以直接进入老年代,不需要达到指定年龄

    5.空间分配担保

    在进行Minor GC之前,虚拟机会检查老年代的最大可用的连续空间是否大于新生区所有对象的总空间,如果这个条件成立,那么这次Minor GC是安全的,如果不成立,虚拟机会查看HandlerPromotionFailure设置值是否允许担保失败,如果允许那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,就尝试着进行一次Minor GC,尽管这次Minor GC是有风险的,如果小于,或者不允许冒险,那么就进行Full GC

    更多内容请看后续
    QQ群:552113611

    相关文章

      网友评论

        本文标题:深入理解java虚拟机(二)垃圾收集器与内存分配策略

        本文链接:https://www.haomeiwen.com/subject/bqkmmftx.html