前言:
垃圾回收机制是Java语言中一个非常重要的机制,在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这些由JVM来处理。那么在java中什么是垃圾?什么是垃圾回收?如何回收?接下来我们一一探讨。
一.什么是垃圾
在Java中,如果一个对象没有被其他对象所引用,那么该对象就会被认为是无用的,就被称为 “ 垃圾 ”。
二.什么是垃圾回收
在以上提到“垃圾”的概念,回收其所占用的内存空间,就叫做 “ 垃圾回收 ”。
三.如何回收垃圾(垃圾回收算法)
1.标记 “垃圾” 的算法
1)引用计数算法
引用计数法就是给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的,可以当做垃圾收集。这种方法实现起来很简单而且优缺点都很明显。
优点:执行效率高
缺点:循环引用的情况下无法进行回收,会导致内存泄漏
循环引用是指两个对象互相引用。如果这两个对象都没有在其他任何地方被使用,即没有价值了,但是它们互相引用了,导致GC回收不了内存,容易造成内存泄露。
2)可达性分析算法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则说明该对象是不可用的。
Java中可以作为GC Roots的对象
- 虚拟机栈中的引用对象
- 方法区中的常量引用对象
- 方法区中的类静态属性引用对象
- 本地方法栈中的引用对象
- 活跃线程中的引用对象
- 本地方法栈中JNI(Native方法)引用的对象
2.回收 “垃圾” 的算法
1)标记 -清除算法
“标记-清除”(Mark-Sweep)算法,它是最基础的收集算法,该算法分为“标记”和“清除”两个阶段:
- 标记出所有需要回收的对象。
- 标记完成后统一回收掉所有被标记的对象。
它主要有两个缺点:
- 效率问题,标记和清除过程的效率都不高;
- 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多
2)复制算法
“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当前使用的这一块的内存用完了,就会将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。该方法可避免内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单且高效。
主要的两个缺点:
- 代价是将内存缩小为原来的一半。
- 持续复制长生存期的对象则导致效率降低。
3)标记-整理算法
为了解决复制算法的缺陷,充分利用内存空间,提出了标记整理算法。该算法标记阶段和标记清除一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
4)分代收集算法
“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。当前商业虚拟机都是采用该种算法进行垃圾回收。
涉及概念:
- GC的分类:
- Minor GC:从新生代回收内存。
- Full GC:回收整个Java堆空间的内存,包括新生代和老年代。Full GC比Minor GC慢很多,大概有10倍的差距,但是Full GC执行的频率比较低。
- Major GC:一般来说和Full GC是等价的,由于名词解读的混乱,当说Major GC时一定要问清楚是指Full GC还是仅指从老年代回收内存。
- 触发Full GC的条件:
- 老年代空间不足
- 永久代空间不足(JDK1.7之前)
- CMS GC时出现promotion failed,concurrent mode failure时
- Minor GC晋升到老年代的平均大小大于老年代的剩余空间
- 调用System.gc()
- 使用RMI来进行RPC或管理的JDK应用,没小时执行一次Full GC
Java 堆(Java Heap)是JVM所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,Java 堆主要分为2个区域-年轻代与老年代,其中年轻代又分 Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2个区。
- Eden 区
大多数情况下,对象会在新生代 Eden 区中进行分配,当 Eden 区没有足够空间进行分配时,虚拟机会发起一次 Minor GC,Minor GC 相比 Major GC 更频繁,回收速度也更快。 通过 Minor GC 之后,Eden 会被清空,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。
- Survivor 区
Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交通灯中的黄灯。Survivor 又分为2个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)。Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。Survivor 的预筛选保证,只有经历16次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。
- Old 区
老年代占据着2/3的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记——整理算法。
对象如何晋升到老年代:
- 经历一定Minor次数依然存活的对象,默认:15。
- Survivor空间中存放不下的对象,会存储在老年代。
四.垃圾收集器
涉及概念:
- Stop The World :
- JVM由于要执行GC而停止应用程序的执行。
- 任何一种GC算法中都会发生。
- 多数GC优化都是通过减少stop-the-world发生的时间来提高程序性能。
1.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
CMS收集器是基于“标记-清除”算法实现的,它的运作过程分为4个步骤:
- 初始标记(CMS initial mark),需要“Stop The World”,初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快
- 并发标记(CMS concurrent mark),进行GC Roots Tracing的过程
- 重新标记(CMS remark),需要“Stop The World”,修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一 般会比初始标记阶段稍长一些,但远比并发标记的时间短。
- 并发清除(CMS concurrent sweep)
由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行。
2.G1收集器
与CMS收集器相比G1收集器有以下特点:
- 空间整合。G1收集器采用标记整理算法,不会产生内存空间碎片;
- 可预测停顿。这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒;
- 年轻代和老年代不再物理隔离;
- 分代收集