程序计数器、虚拟机栈、本地方法栈这三个区域的生命周期是和线程同步的,并且内存分配是在编译期就知道了,所以在方法结束或线程结束时,这三个区域的内存自然就回收了。而Java堆和方法区是在程序运行时才动态分配和回收内存,垃圾收集器所关注的就是这部分的内存。
判断对象是否死亡
堆中几乎放着所有的对象实例。垃圾收集器判断堆中对象是否“死去”有这几种方法:
1、引用计数法
当有一个地方引用该对象,计数器就加1,引用失效,则计数器减1,当计数器为0时表示对象“死亡”。(Java并没有使用引用计数法,因为它很难解决对象之间相互引用的问题)
2、根搜索法
通过一系列“GC Root”对象作为起点,向下搜索,当GC Root到这个对象不可达时,表示该对象“死亡”。(主流的程序都是该方法)
垃圾收集算法
1、标记-清除算法
首先标记处所需要回收的对象,完成之后统一回收掉所有被标记的对象。不过它有两个缺点:效率低、空间浪费(产生大量不连续的内存碎片)
2、复制算法
将内存划分大小相等的两块,每次只使用其中一块,每次回收后将存活的对象复制到另一块内存上。这种运行效率高,代价是要浪费了一半的内存空间。
3、标记-整理算法
该算法的标记部分和“标记-清除”一样,不过标记完不是直接清除,而是让所有存活的对象都向一端移动,然后直接清理掉边界以为的内存。
4、分代收集算法
当前的商业虚拟机都是使用该算法,一般将内存分为新生代、老年代,根据各年代不同的特点使用不同的算法。新生代就使用复制算法,老年代使用“标记-清除”或“标记-整理”算法。
垃圾收集器
Hotspot JVM 1.6 的垃圾收集器
两个收集器之间存在连线,表示可以搭配使用。这些收集器各有特点,我们需要在具体场景下选用最好的收集器。
1、Serial收集器
单线程收集器,垃圾收集时其他工作必须暂停。运行时线程如下图所示。
Serial收集器是Client模式下默认的新生代收集器,由于其简单高效,在桌面应用中使用内存一般比较小,用该收集器是不错的选择。
Serial/Serial Old收集器
2、ParNew收集器
ParNew收集器是Serial收集器的多线程版本,是Sever模式下首选的新生代收集器。除了Serial外,只有它能和CMS收集器配合,也是使用CMS后默认的新生代收集器。
刚才说了ParNew是Serial的多线程,因此在单CPU下,其实Serial是更好的选择,不过目前单CPU的机器基本绝迹了。
-XX:+UseParNewGC或使用+XX:+UseConcMarkSweepGC的默认新生代收集器)
ParNew/Serial Old收集器
3、Parallel Scavenge收集器
它是使用复制算法的收集器,也是并行的多线程收集器。Parallel Scavenge收集器月ParNew的区别主要是它目标是达到可控的吞吐量,而其他的收集器是尽可能缩短用户线程停顿时间。
4、Serial Old收集器
Serial Old是Serial的老年代版本,同样是单线程的,垃圾收集使用“标记-整理”算法。主要也是在Client模式下使用,在Serial中的图同时显示了Serial Old的运行时的线程。
5、Parallel Old收集器
Parallel Old是Parallel Scavenge的老年代版本,垃圾收集使用多线程和“标记-整理”算法。在注重吞吐量及CPU资源敏感的场景,可以优先选择Parallel Scavenge+Parallel Old的组合。
Parallel Scavenge/Parallel Old收集器
6、CMS收集器
CMS(Concurrent Mark Sweep)目标是获取最短停顿时间的收集器,是目前B/S中服务端最流行的收集器。
CMS是基于“标记-清除”的算法实现的,可以分为4个步骤:
a. 初始标记:标记GC Roots能关联到的对象
b. 并发标记:进行GC Roots Tracing,判断对象是否死亡
c. 重新标记:修正并发标记期间因用户程序继续运行,导致标记变动的那些对象
d. 并发清除
CMS收集器
通过上述可以知道“初始标记”和“重新标记”是需要暂停用户线程的,而“并发标记”和“并发清除”是收集器线程与用户线程一起工作的。不过1、3步骤耗时较短,主要的2、4步骤是并发执行的,可以说CMS的内存回收过程是与用户线程一起执行的。
CMS虽然可以说是并发低停顿收集器,当还是有一些缺点:
l 对CPU资源敏感:由于并发收集,占用CPU资源而导致程序变慢,总吞吐量降低。
l 无法处理浮动垃圾(标记过程后产生的垃圾),导致可能出现Full GC。所以CMS GC时需要预留一部分空间,即在空间使用到一定比例时就需要GC操作。
l CMS是基于“标记-清除”的算法,会出现大量的空间碎片。(内存整理的过程是无法并发的)
7、G1收集器
G1的目标是能替代CMS收集器,但在JDK7正式发布之后,仍然没有摆脱Experimental的标签。它与CMS相比有两个显著的改进:一、G1是基于“标记-整理”算法实现;二、可以非常精确地控制停顿。
它不再将Java堆分为新生代、老年代,而是分为多个大小固定的区域(Region),跟踪这些区域,优先回收垃圾最多的区域。
回收策略
上面已经描述了堆内存分为三部分:新生代、旧生代、持久代。Java程序启动的时候,ClassLoader信息会放入持久代,新建的对象会放入新生代,当新生代满了(Eden满了),会引发Minor GC(YGC),YGC后还存活的对象将被移动到旧生代,而当旧生代满了就会触发Major GC(Full GC)。
Full GC是整个heap的回收,包括了旧生代和新生代,而且旧生代一般占存储空间比较大,收集时间长,频繁的Full GC会对应用的性能产生较大的影响,所以要尽量减少Full GC的次数。
Minor GC(YGC)
1、大部分对象创建后先进入Eden Space,Eden Space空间不足将会触发YGC。
2、YGC垃圾回收时,扫描Eden Space和S0进行回收,如果对象还存活,将其复制到S1。如果S1已满则复制到旧生代
3、扫描S0时,如果对象已经过了几次扫描还存活(超过turning threshold值),则移到旧生代
4、扫描完毕将Eden Space和S0清空,下次YGC时将S0、S1角色对换,从步骤2开始重复GC操作
这种机制就不需要每次GC都将内存所有对象检查一遍了,避免了像Full GC那样对应用的性能产生较大的影响。
Major GC(Full GC)
由于旧生代或持久代空间不足等原因,将会触发Full GC。回收机制根据各收集器而定,上面垃圾收集器部分已经说了具体过程。