Try Everything Different In My Life.

「📝总结」随话Java对象GC的事

2019.08.19

‘Java’对象垃圾回收是一个系统的问题,有前后的联系关系,所以要前后串联起来看。

在说对象之前

对象出生之前得准备好很多东西,怎么出生,出生在哪里,这些都是需要提前说清楚的,不然这么唐突的说对象有点准备不及

首先要说的是JMM,Java为了实现Java的一些特性(如GC,高并发。。。)设计了JMM,JMM内容很多,今天只说JMM对于内存空间的分代

为什么JMM要对内存进行分代呢?为了GC方便,怎么分的呢?分为新生代和老年代(之前还有一个永久代,Java8中给取消了),新生代中又分了三块,From survivor,eden,to survivor,比例是1:8:1

GC主要回收的内存区域有两块,一个堆还有一个是方法区

对象出生

说了内存分代,那现在就来创建对象吧。当我要使用一个对象的时候,我们的类加载器就找到对应的class文件,进行类加载的过程,加载,验证,分析,初始化。。。这个时候我们对象就有了,默认在新生代创建新对象

创建对象要给你对象分配空间吧,怎么分配的呢?一般有两种

  • 指针碰撞
  • 空闲列表

指针碰撞就相当于便利内存,找到大小合适的连续内存块就将对象放到那边去,空闲列表就是记录一下内存的使用情况,直接找到对应的地方

到这里对象已经好了可以使用了

对象死了吗?

对象在使用之后可能会被再次使用,也可能永远不会被使用,对于不能使用的对象我们要做回收,那回收之前我们怎么知道对象死了没啊,我们主要有两种方法

  • 引用计数法
  • 可达性分析法

引用计数就是对象每被引用一次就+1,失去隐去就-1,最后变成0的时候说明这个对象没有人使用了,但是这个方法存在弊端,循环引用的时候就不适用了

可达性分析就是从GC ROOT这个起点找,如果对象不在引用链中的话就说明对象危险了,可能要被回收了,Java中使用的方法就是可达性分析法

进过可达性分析之后,对象的引用被分成以下4种:

  • 强引用
  • 软引用
  • 弱引用
  • 幻想引用

这种状态和最后回收与否息息相关

回收吧

经过上面的分析之后我们可以知道对象的状态了,下面就开始回收吧,回收也有好几种方法,他们之间不是完全独立的,都是息息相关的

  • 标记-清除算法
  • 标记-复制-清除算法
  • 标记-整理-清除算法
  • 分代算法

标记-清除算法基本上就是后面几个算法的鼻祖,只是在清除之前做了一些其他的事。标记-清除就是把要回收的对象标记出来,然后回收对应的对象,这种算法有一个坏处就是回收完空闲内存是不连续的,有很多内存碎片

标记-复制-清除算法就是在清除之前将存活的对象复制一份到独立的空闲的内存上,然后将之前那块内存全部擦除就好了,这下没有内存碎片了,但是要维护一个空闲的内存,所以内存使用率永远不会是100%,并且一般维护的空闲内存不大,所以存活的对象应该不多

标记-整理-清除算法也有叫做压缩算法的,本质就是将存活的对象移动到一端,然后清除指针另一边的所有内存擦除,这样也不要维护复制算法中的空闲内存了

分代回收其实就是集大成者,前面不是说了JMM将Java的堆内存分为新生代和老年代吗,新生代和老年代都有自己的特点,新生代的对象来的快走的也快,吞吐量比较大,老年代的对象生命周期比较长,对象比较大。依据这些特点使用不同回收算法

GC收集器:开干

上面只是说怎么做,但是不知道谁去做,Java是自动管理内存的,当然是Java去帮我们完成了,这个东西JVM的GC收集器,本质是就是对上面算法的实现,有以下四种收集器

  • Serial(串行) GC
  • ParNew(并发) GC
  • Parallel(并行) GC
  • CMS(并发) GC
  • G1

先说Serial GC,一句简单的来说就是stop the world,他工作的时候要所有的线程都停止,然后开始收集垃圾,新生代使用复制算法,老年代使用Serial old 收集器使用标记-整理算法。单线程设计简单,初始化简单,所以一般都是client模式下的默认选项

使用如下参数可以开启serial gc

-XX:+UseSerialGC

再说Parnew GC实际上就是多线程版本的Serial GC,适用于新生代的垃圾回收,常用场景是配合老年代的CMS GC工作

-XX:+UseConcMarkSwaapGC -XX:UseParNewGC

Parallel GC在JDK8之前是server模式下默认的JVM首选GC参数,吞吐量优先的GC,特点是新生代和老年代并行回收

-XX:UseParallelGC

CMS基于标记-清除算法,特点是为了减少停顿时间,但是会有许多内存碎片产生,触发full GC,占用CPU资源高

G1兼顾吞吐量和停顿时间的GC实现,JDK9之后的默认首选,先使用复制算法然后再使用整理算法,避免了内存碎片的产生

总结1-GC收集器比较

名称 运行方式 内存区域 算法 适用环境
Serial收集器 串行 新生代 复制算法 响应速度优先;适用于单CPU环境下的client模式
ParNew收集器 并行 新生代 复制算法 响应速度优先;多CPU环境Server模式下与CMS配合使用
Parallel收集器 并行 新生代 复制算法 吞吐量优先;适用于后台运算而不需要太多交互的场景
Serial Old收集器 串行 老年代 标记-整理算法 响应速度优先;单CPU环境下的Client模式
Parallel Old收集器 并行 老年代 标记-整理算法 吞吐量优先;适用于后台运算而不需要太多交互的场景
CMS收集器 并发 老年代 标记-清除算法 响应速度优先;适用于互联网或B/S业务
G1收集器 并发 新生代或老年代 标记-整理算法+复制算法 响应速度优先;面向服务端应用