Java GC机制

Java GC机制

JVM结构图

JVM结构图

理解GC的意义

  • 什么是GC?
    垃圾回收是指将分配给对象但是不再使用的内存回收或释放的过程,如果一个对象没有指向它的引用或者其赋值为null,则此对象适合进行垃圾回收,简单来说就是为了保证我们程序员开发的项目内存不溢出,让有限的内存空间存放我们需要的对象,将不再使用的对象从内存中释放出来,这就是GC的意义
  • 需要GC的内存区域
    JVM中,程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭,栈帧随着方法的进入和退出做入栈和出栈操作,实现内存的自动清理,因此,我们的内存垃圾回收主要集中于Java堆和方法区中,在程序运行期间,这部分内存的分配和使用都是动态的。
  • GC的对象
    需要进行回收的对象就是已经没有存活的对象,判断一个对象是否存活常用的有两种方法:引用计数法和可达性分析。
    (1)引用计数:每个对象有一个引用计数属性,新增一个引用计数时加1,引用释放时计数减1,计数为0时可以会输。此方法简单,无法解决对象相互循环引用的问题。
    (2)可达性分析:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。
    在Java语言中,GC Roots包括:
    虚拟机栈中引用的对象。
    方法区中类静态属性实体引用的对象。
    方法区中常量引用的对象。
    本地方法栈中JNI(本地方法接口)引用的对象。
  • 什么时候触发GC
    (1)程序调用System.gc时可以触发
    (2)系统自身来决定GC触发的时机(根据Eden和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程)
    GC又分为轻GC和重GC
    轻GC触发条件:当Eden区满时,触发轻GC
    重GC触发条件
    a.调用System.gc时,系统建议执行重GC,但是不必然执行
    b.老年代空间不足
    c.方法区空间不足
    d.通过轻GC进入老年代的平均大小大于老年代的可用内存
    e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代可用内存小于该对象大小
    堆内存图示:
    堆内存图示
    GC垃圾回收主要在伊甸园区和养老区,假设内存满了则会报堆内存不够的异常,也就是OOM
    Java.lang.OutOfMemoryError:Java heap space
    在JDK8以后,永久存储区改了个名字(元空间)
    新生区
    1.类诞生和成长的地方,甚至死亡
    2.伊甸园区,所有的对象都是在伊甸园区new出来的
    3.幸存区(0,1)也就是我们常说的From Space 和To Space,哪边内存为空哪边则是To Space
    每次GC都会将Eden活的对象移到幸存区中,一旦Eden区被GC后就会是空的
    -xx:-xx:MaxTenuringThreshold=15,通过这个参数可以设定进入老年代的时间。
    永久区
    这个区域是常驻内存的,用来存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭VM虚拟机就会释放这个区域的内存(注意,JDK1.8以后无永久代,常量池在元空间)
    上面提到的元空间实际上并不是替代了永久代,他逻辑上是存在的但是物理上并不存在,因为它与堆共享着内存,元空间也叫“非堆”
  • GC常用算法
    GC常用的算法有:标记-清除算法标记-压缩算法复制算法分代收集算法。目前主流的JVM(HotSpot)采用的是分代收集算法
    1.标记-清除算法
    为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段堆死亡的对象进行清除执行GC操作,图示如下:
    标记-清除算法图解
    优点
    最大的优点是,标记-清除算法中每个活着的对象的引用只需要找到一个即可,找到一个就可以判断它为活的。此外,更重要的是,这个办法并不需要移动对象的位置,不需要额外的空间。
    缺点
    它的缺点是效率比较低(递归与全堆对象遍历)。每个活着的对象都要在标记阶段遍历一遍;所有对象都要在清除阶段扫描一遍,因此算法复杂度较高,严重浪费时间(两次扫描)。没有移动对象,导致可能出现很多碎片空间无法利用的情况
    2.标记-压缩算法(标记-整理)
    标记-压缩法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。
    图示可以说是接着上面的标记清除之后的:
    标记-压缩算法图解
    优点
    该算法不会像标记-清除算法那样产生大量的碎片空间
    缺点
    如果存活的对象过多,整理阶段将会执行较多的复制操作,导致算法效率降低(多了一个移动的成本)
    3.复制算法
    该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个区域内。
    优点
    实现简单,不产生内存碎片
    缺点
    每次运行,总有一半内存是空的,导致可以使用的内存空间只有原来的一半,因此最好用于对象存活率比较低的时候也就是新生区
    4.分代收集算法
    现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存周期短,每次回收都会有大量的对象死去,那么这时候就应该采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理或者标记-清除算法

垃圾收集器

如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现

1. Serial收集器
串行收集器是最古老,最稳定以及效率高的收集器
可能会产生较长的停顿,只使用一个线程区回收
-XX:+UseSerialGC

  • 新生代、老年代使用串行回收
  • 新生代复制算法
  • 老年代标记-压缩
    图示:
    Serial收集器 2. 并行收集器
    2.1 PartNew
    -XX:+UseParNewGC(new代表新生代,所以适用于新生代)
  • 新生代并行
  • 老年代串行
    Serial收集器新生代的并行版本
    在新生代回收时使用复制算法
    多线程,需要多核支持
    -XX:ParallelGCThreads限制线程数量
    2.2 Parallel收集器
    类似PartNew
    新生代复制算法
    老年代标记-压缩
    更加关注吞吐量
    -XX:UseParallelGC
  • 使用Parallel收集器+老年代串行
    -XX:UseParallelOldGC
  • 使用Parallel收集器+老年代并行
    2.3 其他GC参数
    -XX:MaxGCPauseMills
  • 最大停顿时间,单位毫秒
  • GC尽力保证回收时间不超过设定值
    -XX:GCTimeRatio
  • 0-100的取值范围
  • 垃圾收集时间占总时间的比
  • 默认99.即最大允许1%时间做GC
    这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优
    3. CMS收集器
  • Concurrent Mark Sweep并发标记清除(应用程序和GC线程交替执行)
  • 使用标记-清除算法
  • 并发阶段会降低吞吐量(停顿时间减少,吞吐量降低)
  • 老年代收集器(新生代使用ParNew)
  • -XX:+UseConcMarkSweepGC
    CMS运行过程比较复杂,着重实现了标记的过程,可分为
    1.初始标记(会产生全局停顿)
    • 根可以直接关联到的对象
    • 速度快
      2.并发标记(和用户线程一起)
    • 主要标记过程,标记全部对象
      3.重新标记(会产生全局停顿)
    • 由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
      4.并发清除(和用户线程一起)
    • 基于标记结果,直接清理对象
      这里就能很明显的看出,为什么CMS要使用标记清除而不是标记压缩,如果使用标记压缩,需要多对象的内存位置进行改变,这样程序就很难继续执行。但是标记清除会产生大量内存碎片,不利于分配内存。
      CMS收集器特点:
      尽可能降低停顿
      会影响系统整体吞吐量和性能(比如,在用户线程运行过程中,分一半CPU区做GC,系统性能在GC阶段,反应速度就下降一半)
      清理不彻底(因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理)
      因为和用户线程一起运行,不能在空间快满时再清理(因为也许在并发GC的期间,用户线程又申请了大量内存,导致内存不够)
    • -XX:CMSlnitiatingOccupancyFraction设置触发GC的阈值
    • 如果不幸内存预留空间不够,就会引起concurrent mode failure
      一旦concurrent mode failure产生,将使用串行收集器作为后备。
      CMS也提供了整理碎片的参数:
      -XX:+ UseCMSCompactAFullCollection Full GC后,进行一次清理
    • 整理过程是独占的,会引起停顿时间变长
      -XX:+CMSFullGCsBeforeCompaction
    • 设置几次Full GC后,进行一次碎片整理
      -XX:ParallelCMSThreads
    • 设定CMS的线程数量(一般情况下约等于可用CPU数量)
      CMS的提出是想改善GC的停顿时间,在GC过程中的确做到了减少GC时间,但是同样导致产生大量内存碎片,又需要消耗大量时间区整理碎片 ,从本质上并没有改善时间。

4. 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的新生代收集和PartNew类似,当新生代占用达到一定比例的时候,开始触发收集。和CMS类似,G1收集器收集老年代对象会有短暂停顿
步骤:
(1)标记阶段,首先初始标记,这个阶段是停顿的,并且会触发一次普通轻GC。对应GC log:GC pause
(2)Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在轻GC之前完成
(3)Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被轻GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那么这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
(4)Remark,再标记,会有短暂停顿(STW)。再标记阶段是用来收集并发标记阶段产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法
(5)Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。
(6)复制/清除过程后。回收区域的活性对象已经被集中回收。

finalize()方法详解

1.finalize的作用
(1)finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC再回收对象之前调用该方法。
(2)finalize()与C++中的析构函数不是对应的。Java中的finalize的调用具有不确定性
(3)不建议用finalize()方法完成“非内存资源‘的清理工作,但是建议用于:

  • 清理本地对象(通过JNI创建的对象);
  • 作为确保某些非内存资源(如Socket、文件等)释放的一个补充:在finalize()方法中显式调用其他资源释放方法。

2 finalize的问题
(1)一些与finalize相关的方法,由于一些致命的缺陷,已经被废弃了,例如System.runFinalizersOnExit()方法等
(2)System.gc()与System.runFinalization()方法增加了finalize()方法执行的机会,但是不可盲目依赖他们
(3)Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行
(4)finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行
(5)对象再生问题:finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,但是并不影响GC对finalize的行为。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值