导读:我相信很多朋友都有一个疑问“我会写代码就好了呀,为什么要学JAVA垃圾收集器?难道是为了应付面试吗?” 我曾经也一脸懵B的想过这个问题,所以让我们一起来寻找答案吧…
解开心中的疑问
(1). 为什么需要JAVA垃圾回收机制?什么是JAVA垃圾收集器?
- “传说不是JAVA自动清理垃圾么?我就冲着这点学的JAVA,怎么现在还要我们关注和了解垃圾回收?”答案很简单:当需要排查各种内存益处、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
- 垃圾收集(Garbage Collection,简称GC):GC中的垃圾,特指存在于内存中的、不会再被使用的对象,而“回收”,也相当于把这些不再被使用的对象清空掉。如果不及时进行垃圾回收,这些垃圾对象所占用的内存就无法得到释放,当大量内存被垃圾占用后,新对象无法得到内存,这时就会造成内存溢出。
(2). 哪些内存需要回收?什么时候回收?如何回收?
- JAVA堆和方法区,由于一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有再程序处于运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾回收器所关注的就是这部分的内存。
- 虚拟机利用可达性分析算法,当一个对象到GC Roots没有任何引用链相连接时,这个对象视为回收对象。
- JAVA使用
finalize()
方法,将待回收的对象放置在F-Queue队列中,并再稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行,以此来进行垃圾回收。
(3). 什么是新生代?什么是老年代?什么时候回收对象由新生代进入老年代?
- 新生代:存放年轻对象的堆空间。年轻对象指刚刚创建的,或者经历垃圾回收次数不多的对象。
- 老年代:存放老年对象的堆空间。老年对象指经历过多次垃圾回收依然存活的对象。
- 有两种对象会进入老年代,一种是:经过多次标记依然存活的对象;另外一种是:很大的对象,它不会进入新生代,而是直接进入老年代。
深入深入再深入
JAVA虚拟机是如何来判断对象的存活与否的呢?这就要谈到“引用”了。
引用的分类以及概念
- 强引用:是指再程序代码之中普遍存在的,只要强引用存在,垃圾收集器永远不会收掉被引用的对象。
例如:StringBuffer str = new StringBuffer("Hello World");
- 软引用:是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,再系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
public class ReferenceTest {
public static void main(String[] args) {
//创建强引用user
User user = new User(1, "qnloft");
SoftReference<User> objSoftReference = new SoftReference<>(user);
//通过强引用user,建立软引用
System.out.println(objSoftReference.get());
}
}
class User {
int age;
String name;
public User(int age, String name) {
this.age = age;
this.name = name;
}
}
- 弱引用:用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
- 虚拟引用:是最弱的一种引用关系。一个对象是否有虚引用存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象实例。虚引用存在的唯一目的就是能在这个对象被回收时收到一个系统通知。
了解了如何判断对象存活之后,我们来看看垃圾回收的一些算法,大题了解下垃圾回收的机制。
常用垃圾回收算法优缺点一览:
- 标记-清除算法:算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,再标记完成后统一回收所有被标记的对象。
优点:实现简单,判定效率高 ;
缺点: 1.效率低 2.空间问题:标记和清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要分配较大对象时,无法找到足够的内存而不得已触发另一次垃圾手机动作。 - 复制算法:它可以将内存按容量划分为大小相等的两块,然后再把已使用过的内存空间一次清理掉。
优点:实现简单,运行效率高;
缺点:将内存缩小为原来的一半,代价高 - 标记-整理算法:标记过程与“标记-整理算法”一致,后续是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
- 分代收集算法:一般把JAVA堆划分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。再新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。
如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

(1). 新生代垃圾回收器
- Serial回收器(串行回收器):是历史最悠久的垃圾回收器。特点:收集器是单线程,独占式。它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop The World)。优点:简单而高效。是Client模式下默认的垃圾回收器。

- parNew回收器(并行回收器):Serial收集器的多线程版本。在并发能力比较强的CPU上,能力大于Serial收集器.

- Parallel GC回收器(并行回收器):使用
”复制算法“
的收集器。特点:非常关注吞吐量,支持自适应的GC调节策略(所谓吞吐量就是CPU运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间))。自适应策略即:只需要把基本的内存参数设置好(如-Xmx设置最大堆),然后使用MaxGCpauseMillis参数或GCTimeRatio参数给虚拟机设立一个优化目标,具体参数细节就由虚拟机完成了。
(2). 老年代垃圾回收器
- Serial Old回收器(串行回收器):同样是单线程收集器,使用
”标记-整理“
算法。用途:作为CMS收集器的备用方案。 - Parallel OldGC回收器(并行回收器):使用
”标记-整理“
算法,与新生代Parallel收集器配合使用,是一个非常关注吞吐量的组合。 - CMS回收器:是一种以获取最短回收停顿时间为目标的收集器。采用
”标记-清除“
算法,同时也是多线程的收集器。特点:并发收集、低停顿。
CMS工作时主要步骤:初始标记、并发标记、重新标记、并发清除。其中,初始标记和重新标记两个步骤仍然需要”Stop The World“。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记的过程则是为了修正并发标记期间因为用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。
缺点:对CPU资源非常敏感;无法处理浮动垃圾,可能导致处理失败而引起另外一次Full GC产生;由于采用”标记-清除“
算法会产生大量空间碎片

(3). G1垃圾回收器:
唯一一个可以独立管理整个GC堆的垃圾回收器,它将整个Java堆划分为多个大小相等的独立区域,虽然还保留有新生代和老年代,但是新生代和老年代已经不再是物理隔离了,他们是一部分独立区域的集合。
特点:
- 并行与并发:使用多个CPU,来缩短Stop The World停顿时间。
- 分代收集:可以采用不用的方式处理新创建的对象和存活了很长时间的对象。
- 空间整合:基于
”标记-整理“
算法实现收集器。 - 可预测的停顿:可以建立可预测是的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗再垃圾手机上的时间不超过N毫秒。
G1工作时主要步骤:初始标记、并发标记、最终标记、筛选回收。
尾巴
简单描述一下垃圾回收
- 在程序中可以调用Sytem.gc()方法进行强制垃圾回收,但是只能回弱引用和虚引用的对象,只有空间不够时才回收软引用对象,強引用对象是无法回收的。
- JAVA虚拟机虽然可以自动回收垃圾对象,但是会对系统造成短暂停顿,即Stop The World,所以代码中如果出现经常创建大对象,则垃圾回收次数会明显增多,造成系统可用性下降。
转载请注明:R&M » JAVA虚拟机之——垃圾收集器与内存分配的概念浅析