Java程序员不得不懂的GC

前言

作为一个Java程序员,绕不过JVM,众所周知 c系程序员自己处理内存申请分配回收,Java 堆和方法区的内存分配,也是通过垃圾收集器去实现的,这里谈下自己对java回收的浅薄了解。

什么是垃圾

  • 垃圾,通俗来讲,即无用对象。用专业术语来讲,没有被任何对象或方法引用(没有指针指向)的对象就是垃圾;

怎么判定垃圾

  • 引用计数算法
    对象每被引用一次,在对象头中记录的count就会+1,反之,减少一次引用就会-1。所以。当一个对象对象头中被引用数count=0时,该对象就是一个垃圾。
    但是如果仅仅用这样的算方式,有些没有被实际引用的对象不会被发现(循环依赖);A->B->C->A ,ABC三个对象,相互引用,但是都不被虚拟机栈中任何一个对象或方法引用。ABC都是垃圾对象。
    总结:引用计数法无法解决循环依赖
  • 根可达性算法
    从GCRoot向下搜索能够找到的对象,都是有用对象。反之,不能找到的,都是垃圾。
    可作GCRoots的对象(堆中引用的不能作为GCRoot对象)
    • 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
    • 方法区中的类静态属性引用的对象。
    • 方法区中的常量引用的对象。
    • 本地方法栈中JNl(即一般说的Native方法)的引用的对象

垃圾回收的区域

  • 垃圾回收一般发生在堆和方法区(线程共享的区域)。方法区也是有垃圾回收的,主要回收废弃常量(比如字符串常量,没有对象引用即可回收)和无用的类(所有的实例都已经被回收、该类的ClassLoader已经被回收、该类对应的java.lang.Class对象在任何地方没有被引用,也无法通过反射访问该类的方法)

垃圾回收前的两次标记

  • 第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记;
  • 第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize() 方法。在 finalize() 方法中没有重新与引用链建立关联关系的,将被进行第二次标记,第二次标记成功的对象将真的会被回收。

垃圾回收的几种算法

标记-清除算法(Mark-Sweep)

  • 主要分为标记和清除2个阶段。首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。如下图(excel画的图比较粗糙,理解一下) 利用根可达性算法标记垃圾对象,然后对垃圾进行清除。
  • GC前在这里插入图片描述
  • GC后
    在这里插入图片描述
    由上图可见,标记清除算法的缺也是显而易见的:产生来大量的内存碎片。

复制回收算法(Copying)

将可用内存按容量划分为相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

  • GC前
    在这里插入图片描述
  • GC后
    在这里插入图片描述
    优点:效率高、清理后内存连续
    缺点:需要将可用内存空间一分为二,浪费内存空间

标记-整理算法(Mark-Compact)

也叫标记压缩算法,标记过程仍然与“标记-清除”算法一样,然后让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

  • GC前在这里插入图片描述
  • GC后
    在这里插入图片描述
    优点:内存连续,且全部内存空间可用。
    缺点:垃圾清除的过程还要移动对象(复制对象),效率低

分代回收算法

其实是根据虚拟机对中对象的分代特性,混合使用以上回收算法,当前商业虚拟机都是采用这种算法。新生代(新陈代谢快、垃圾占比高)每次垃圾回收都有大量对象失去,选择复制算法。老年代对象存活率高,无人进行分配担保,就必须采用标记清除或者标记整理算法

  • 新生代GC:minorGC(Young GC)
  • 老年代GC:majorGC(Old GC)
    majorGC效率比minorGC慢10倍,所以避免发生majorGC。
  • fullGC:整个堆(minorGC和majorGC)和方法区的垃圾回收。老年代不够用,没人替它担保,所以会触发fullGC
    majorGC和FullGC经常混为一谈。因为一般来说发生了majorGC前,先要来一次minorGC。一般来说,内存担保前发生了一次minorGC,而如果内存担保1发生之后,发现老年代不够用,相当于会触发majorGC。但是 majorGC只是针对堆,而fullGC不只是针对堆,还会针对方法区
  • STW: Stop The World ,用户线程挂起,GC线程独占CPU进行垃圾回收

垃圾回收器

Serial + Serial Old;

Serial收集器是最基本、发展历史最悠久的收集器,它在JDK1.3.1前是HotSpot新生代收集的唯一选择;依然是HotSpot在Client模式下默认的新生代收集器。
Serial:新生代、单线程、复制算法
Serial Old:老年代、单线程、标记整理算法

应用场景
  • 对单个CPU环境,Serial收集器没有线程切换开销,可获得最高的单线程收集效率;
  • 在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的。
开启选项
-XX:+UseSerialGC

Parallel Scavenge + Parallel Old

并行2收集器组合
Parallel Scavenge:新生代、多线程、复制算法
Parallel Old:老年代、多线程、标记整理算法

应用场景

多CPU的情况下,在注重吞吐量以及CPU资源敏感场景,Parallel Scavenge加Parallel Old收集器表现很强力

开启选项
-XX:+UseParallelGC或-XX:+UseParallelOldGC(可互相激活)

ParNew + CMS

ParNew:新生代、多线程、复制算法
CMS:老年代、多线程、标记清除算法

  • 优点:
    CMS是一款优秀的收集器,主要优点:并发3收集、低停顿
  • 缺点:
    CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。CMS收集器无法处理浮动垃圾,可能会出现“Concurrent Mode Failure”(并发模式故障)失败而导致Full GC产生。CMS是一款“标记–清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC
    在这里插入图片描述
应用场景
  • 与用户交互较多的场景;希望系统停顿时间最短,注重服务的响应速度;
  • 以给用户带来较好的体验;如常见WEB、B/S系统的服务器上的应用
开启选项
-XX:+UseConcMarkSweepGC
配置项
-XX:+UseCMSCompactAtFullCollection 强制进行空间碎片整理 CMS 采用标记算法,会产生大量的空间碎片。
以上参数就是强制执行一次空间碎片整理,但是空间碎片整 理会引发STW。 
-XX:+CMSFullGCsBeforeCompaction 配置经过几次的FullGC进行空间碎片整理
-XX:+CMSFullGCsBeforeCompaction=10 经过10次FGC后进行空间碎片整理,以降低STW次数

G1收集器(整个堆)

回收的内存区域是整个JVM,垃圾收集方式是并行(多线程)方式。在物理内存中没有真正去采用分代的方式去划分内存。G1在物理内存的划分上,采用的分区(Region)方式去划分。默认是2048个分区,可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂)。
分区可以逻辑上被用来作为年轻代内存区域和年老代内存区域,采用的回收算法是复制算法和标记清除标记整理算法。

开启选项
-XX:+UseG1GC

  1. 内存担保:新生代内存分配不够时,需要向老年代贷款(贷的是老年代的内存)如果老年代内存够用,把新生代的对象转移到老生代。 ↩︎

  2. 并行:Parallel 指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态; ↩︎

  3. 并发:Concurrent 指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行)用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上 ↩︎

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值