程序计数器、虚拟机栈、本地方法栈这三个区域的生命周期是和线程同步的,并且内存分配是在编译期就知道了,所以在方法结束或线程结束时,这三个区域的内存自然就回收了。而Java堆和方法区是在程序运行时才动态分配和回收内存,垃圾收集器所关注的就是这部分的内存。


判断对象是否死亡


堆中几乎放着所有的对象实例。垃圾收集器判断堆中对象是否“死去”有这几种方法:

1、引用计数法

当有一个地方引用该对象,计数器就加1,引用失效,则计数器减1,当计数器为0时表示对象“死亡”。(Java并没有使用引用计数法,因为它很难解决对象之间相互引用的问题)

2、根搜索法

通过一系列“GC Root”对象作为起点,向下搜索,当GC Root到这个对象不可达时,表示该对象“死亡”。(主流的程序都是该方法)


垃圾收集算法

1、标记-清除算法

首先标记处所需要回收的对象,完成之后统一回收掉所有被标记的对象。不过它有两个缺点:效率低、空间浪费(产生大量不连续的内存碎片)

2、复制算法

将内存划分大小相等的两块,每次只使用其中一块,每次回收后将存活的对象复制到另一块内存上。这种运行效率高,代价是要浪费了一半的内存空间。

3、标记-整理算法

该算法的标记部分和“标记-清除”一样,不过标记完不是直接清除,而是让所有存活的对象都向一端移动,然后直接清理掉边界以为的内存。

4、分代收集算法

当前的商业虚拟机都是使用该算法,一般将内存分为新生代、老年代,根据各年代不同的特点使用不同的算法。新生代就使用复制算法,老年代使用“标记-清除”或“标记-整理”算法。








垃圾收集器


java 获取jvm对象 java获取jvm内存剩余_java 获取jvm对象

Hotspot JVM 1.6 的垃圾收集器

两个收集器之间存在连线,表示可以搭配使用。这些收集器各有特点,我们需要在具体场景下选用最好的收集器。



1、Serial收集器

单线程收集器,垃圾收集时其他工作必须暂停。运行时线程如下图所示。

Serial收集器是Client模式下默认的新生代收集器,由于其简单高效,在桌面应用中使用内存一般比较小,用该收集器是不错的选择。

java 获取jvm对象 java获取jvm内存剩余_垃圾收集器_02

Serial/Serial Old收集器


2、ParNew收集器

ParNew收集器是Serial收集器的多线程版本,是Sever模式下首选的新生代收集器。除了Serial外,只有它能和CMS收集器配合,也是使用CMS后默认的新生代收集器。

刚才说了ParNew是Serial的多线程,因此在单CPU下,其实Serial是更好的选择,不过目前单CPU的机器基本绝迹了。

-XX:+UseParNewGC或使用+XX:+UseConcMarkSweepGC的默认新生代收集器)

java 获取jvm对象 java获取jvm内存剩余_CMS_03

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的组合。

java 获取jvm对象 java获取jvm内存剩余_垃圾收集器_04

Parallel Scavenge/Parallel Old收集器


6、CMS收集器

CMS(Concurrent Mark Sweep)目标是获取最短停顿时间的收集器,是目前B/S中服务端最流行的收集器。

CMS是基于“标记-清除”的算法实现的,可以分为4个步骤:

a. 初始标记:标记GC Roots能关联到的对象

b. 并发标记:进行GC Roots Tracing,判断对象是否死亡

c. 重新标记:修正并发标记期间因用户程序继续运行,导致标记变动的那些对象

d. 并发清除

java 获取jvm对象 java获取jvm内存剩余_CMS_05

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。回收机制根据各收集器而定,上面垃圾收集器部分已经说了具体过程。