一、什么是垃圾
内存中已经不再被使用的内存空间就是垃圾。而对于虚拟机而言,主要回收的就是堆中产生的垃圾,虚拟机堆的内存主要是通过new生成一个对象的时候,就会在堆中为其分配内存空间,但是当这块空间没有被使用或者引用指向这块内存空间了,这块内存空间就被称为垃圾。
二、如何判定垃圾
一、引用计数法
算法实现:
1、给对象添加一个引用计数器
2、每当有一个地方引用该对象时,计数器的值就加1
3、每当有一个地方引用失效时,计数器的值就减1
4、任何时刻计数器的值为0的对象就是不可能再被使用的
优点:实现简单、效率高
缺点:不能解决对象间循环引用的问题
二、可达性分析算法
![](https://img.haomeiwen.com/i1587509/9ff6a4b63e231c20.png)
算法实现
可达性算法从特定的对象集合(即GC Roots Set)出发,逐一判断对象是否可达。具体来说,分成两个阶段:首先枚举所有GC Roots 对象,然后进行可达性分析。
1、有一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称为“引用链”
2、如果一个对象到GC Roots没有任何引用链相连接时,说明这个对象是不可用的
3、如果一个对象到GC Roots有引用链相连接时,说明这个对象是可用的
GC Root包含以下对象
1、在虚拟机栈中引用的对象
2、在本地方法栈中JNI(即通常说的native方法)引用的对象
3、在方法区中类静态属性引用的对象
4、在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用
5、所有被同步锁(synchronized关键字)持有的对象
6、java虚拟机内部的引用(比如基本数据类型对应的Class对象,常驻的异常对象NullPointExcepiton、OutOfMemoryError等),系统类加载器
OopMap
可以看到GC Root包括挺多类型的,具体的对象数目就更多了。如果在GC时先暂停了用户线程再开始逐个枚举GCRoots对象,肯定需要花费很多时间。有什么方法能够快速枚举GCRoots对象呢?jvm采用OopMap这个数据结构来保存GCRoots对象,在GC时直接通过OopMap就可以得到GCRoots对象了。
那么在什么时候保存GCRoots对象到OopMap呢?jvm引入了安全点概念。当线程运行到安全点时,会保存GCRoots对象到OopMap。进行GC时,GC线程会等所有的用户线程都到达最近的安全点后,再开始进行GC回收,因为只有线程到达安全点,才能保证OopMap是最新的,不会出错。
那么怎么让所有的用户线程都到达最近的安全点呢?有两种方法,抢断式中断和主动式中断。
抢断式中断是先让所有的用户线程全部暂停,如果发现某个线程没有到达安全点,就让它重新运行一段时间再中断,直到它到达安全点为止。
主动式中断是先设置一个标志位,线程运行时不断轮询这个标志位,如果发现标志位为真,就运行到最近的安全点,然后中断挂起。现在几乎所有的jvm都采用主动式中断的方式来中断线程,执行GC操作。
这里面有一种特殊情况,如果某个线程原来就处于睡眠或者阻塞状态怎么办?如果线程原来就处于这种“不运行”的状态,那肯定不能自己主动进行中断了。让GC线程等待“不运行”的线程重新运行,然后再采用主动式中断,从时间上是不可控的。那么还有什么方法吗?jvm引入了安全区的概论。
安全区是指在一段代码片段之中,引用关系是不变的。因此,在这个区域中任意地开始垃圾回收都是安全的。当用户线程执行到安全区里面的代码时,会首先标识自己已经进入了安全区域。如果线程要离开安全区时,需要检查是否已经完成了GC。如果完成了,线程可以继续执行,如果没有完成,线程就需要一直等待,直到收到可以离开安全区的信号为止。
通过暂停所有的用户线程,安全点和安全区,jvm保证了在进行GC时,引用关系是不变的,可以获取到最新的OopMap。获取到最新的OopMap后,可以得到所有GC Roots对象的集合,然后就可以进行可达性分析了。
三、引用分类
1、强引用(“Strong” Reference),就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,当然具体回收时机还是要看垃圾收集策略。
2、软引用(SoftReference),是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
3、弱引用(WeakReference)并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如,维护一种非强制性的映射关系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。对于幻象引用,有时候也翻译成虚引用,你不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制
4、幻象引用,有时候也翻译成虚引用,你不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制
四、垃圾回收算法
1、标记-整理
![](https://img.haomeiwen.com/i1587509/8e9855036eef2a7b.png)
![](https://img.haomeiwen.com/i1587509/a272474f8f582359.png)
执行步骤:
标记:对需要回收的进行标记
整理:让存活的对象,向内存的一端移动,然后直接清理掉没有用的内存。
2、复制
![](https://img.haomeiwen.com/i1587509/8085bf32bc5b50d2.png)
![](https://img.haomeiwen.com/i1587509/10595f66f9b04d15.png)
3、标记清除
![](https://img.haomeiwen.com/i1587509/079c4597cc4e94d5.png)
![](https://img.haomeiwen.com/i1587509/0cb799d2a0abd81d.png)
执行步骤:
标记:遍历内存区域,对需要回收的对象打上标记。
清除:再次遍历内存,对已经标记过的内存进行回收。
优点
相对于标记–清理算法解决了内存的碎片化问题。
效率更高(清理内存时,记住首尾地址,一次性抹掉)。
缺点:
效率问题;遍历了两次内存空间(第一次标记,第二次清除)。
空间问题:容易产生大量内存碎片,当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发GC。
五、垃圾收集器
Serial GC
它是最古老的垃圾收集器,“Serial”体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的 GC 实现,无需维护复杂的数据结构,初始化也简单,所以一直是 Client 模式下 JVM 的默认选项。从年代的角度,通常将其老年代实现单独称作 Serial Old,它采用了标记 - 整理(Mark-Compact)算法,区别于新生代的复制算法。
![](https://img.haomeiwen.com/i1587509/844313788210d92f.png)
Serial GC 的对应 JVM 参数是:
-XX:+UseSerialGC
ParNew GC
是个新生代 GC 实现,它实际是 Serial GC 的多线程版本,最常见的应用场景是配合老年代的 CMS GC 工作.是server模式下首选的新生代收集器
![](https://img.haomeiwen.com/i1587509/df2ebbf5c0f325c6.png)
对应参数如下:
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
CMS(Concurrent Mark Sweep) GC
基于标记 - 清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间,这一点对于 Web 等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用 CMS GC。但是,CMS 采用的标记 - 清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生 full GC,导致恶劣的停顿。另外,既然强调了并发(Concurrent),CMS 会占用更多 CPU 资源,并和用户线程争抢。
![](https://img.haomeiwen.com/i1587509/8e87814662e3af70.png)
Parallel GC
在早期 JDK 8 等版本中,它是 server 模式 JVM 的默认 GC 选择,也被称作是吞吐量优先的 GC。它的算法和 Serial GC 比较相似,尽管实现要复杂的多,其特点是新生代和老年代 GC 都是并行进行的,在常见的服务器环境中更加高效。开启选项是
C1
这是一种兼顾吞吐量和停顿时间的 GC 实现,是 Oracle JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标,相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。G1 吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时 CMS 已经在 JDK 9 中被标记为废弃(deprecated)
![](https://img.haomeiwen.com/i1587509/9f72703b976fc045.png)
网友评论