Java 可以自动的进行垃圾回收,不需要开发人员手工处理,这是Java的优势之一。那么到底啥是垃圾回收,她又是如何进行的呢?
其实很简单,Java的运行是基于JVM虚拟机的,JVM中垃圾回收主要分两步:
- 查找内存中不再使用的对象
- 释放废弃对象占用的内存
一、查找内存中不再使用的对象
1、引用 计数法
引用计数法就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。
2、根搜索算法
根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
二、释放废弃对象占用的内存
找到垃圾对象了,接下来就是清理。常见的方式有复制或者直接清理,但是直接清理会存在内存碎片,于是就会产生了清理再压缩的方式。
1、标记-复制
它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。它的优点是实现简单,效率高,不会存在内存碎片。缺点就是需要2倍的内存来管理。
2、标记-清理
标记清除 算法 分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。它的优点是效率高,缺点是容易产生内存碎片。
3、标记-整理
标记操作和“标记-清理”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有 存活的对象都向一端移动,并更新引用其对象的指针。因为要移动对象,所以它的效率要比“标记-清理”效率低,但是不会产生内存碎片。
另外,还有基于分代的假设。由于对象的存活时间有长有短,所以对于存活时间长的对象,减少被gc的次数可以避免不必要的开销。这样我们就把内存分成新生代和老年代,新生代存放刚创建的和存活时间比较短的对象,老年代存放存活时间比较长的对象。这样每次仅仅清理年轻代,老年代仅在必要时时再做清理可以极大的提高GC效率,节省GC时间。
三、垃圾收集器的历史
1、Serial(串行)收集器
在jdk1.3.1之前,java虚拟机仅仅能使用Serial收集器。 Serial收集器是一个单 线程 的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
PS:开启Serial收集器的方式
-XX:+UseSerialGC
2、 Parallel (并行)收集器
Parallel收集器也称吞吐量收集器,相比Serial收集器,Parallel最主要的优势在于使用 多线程 去完成垃圾清理工作,这样可以充分利用多核的特性,大幅降低gc时间。
PS: 开启Parallel收集器的方式
-XX:+UseParallelGC -XX:+UseParallelOldGC
3、CMS(并发)收集器
CMS收集器在Minor GC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。在Full GC时不再暂停应用线程,而是使用若干个后台线程定期的对老年代空间进行扫描,及时回收其中不再使用的对象。
PS: 开启CMS收集器的方式
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
4、G1(并发)收集器
G1收集器(或者垃圾优先收集器)的设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。
PS:开启G1收集器的方式
-XX:+UseG1GC
其中G1收集器是最新型的垃圾收集器,颠覆了之前的方式,大家感兴趣可以去了解下。