Java与C++之间有一堵内存动态分配和垃圾收集技术所围成的‘高墙’,墙外面的人想进去,墙里面的人想出来。 ——《深入理解JVM虚拟机》
对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,由虚拟机管理内存这一切看起来都很美好。不过,也正是因为Java程序员把内存控制的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会成为一项异常艰难的工作。
Java的堆和方法区不一样,只有在运行期间才会知道创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存,也就是说堆和方法区是垃圾回收的主要区域。
**垃圾收集器要完成两件事:**一件事能够正确的检测出垃圾对象。另一件事能够正确的释放垃圾对象占用的内存空间。
常见的垃圾收集算法:
- 标记-清除法
- 复制算法
- 标记-整理法
- 分代收集算法
hotspot(Sun JDK和OpenJDK中所使用的的虚拟机)使用的是基于分代的垃圾收集算法。
Java对象引用:
在JDK1.2以前,Java引用的定义很传统:如果reference类型的数据中存储的是另一块内存的起始地址 ,则称这块内存代表着一个引用。一个对象在这用定义下只有引用和被引用两种状态。在JDK1.2以后,Java将引用分为了强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种。
- 强引用是指代码中普遍存在的,类似于"Object obj=new Object()"这类引用,只要强引用还存在,垃圾收集器永远不会回收这类对象。
- 软引用用来描述一些还存在但不是必须的对象,软引用关联的对象,在系统将要发生内存溢出之前,将会把这些对象列入回收范围中进行二次回收,如果还没有足够的内存才会回抛出内存溢出异常。softReference类来实现软引用。
- 弱引用也是来描述非必须对象的,但是它的强度比软引用更弱一些,它是最弱的一种引用关系,被若引用关联的对象只能生存到下一次垃圾回收之前。在垃圾收集器执行时,无论内存是否够用都会回收若引用关联的对象。 WeakReference来实现弱引用。
- 虚引用也称幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用来获取一个对象实例。为一个对象设置虚引用的唯一目的是就是在这个对象被收集器回收时收到一个系统通知。PhantomReference来实现虚引用。
一、如何检测垃圾
只要是某个对象不被其他活动对象引用,那么这个对象就可以被回收了。这里活动对象指的是能够被一个根对象集合到达的对象。如下图所示,除了f和h对象之外,其他都可以成为活动对象,因为它们都可以被根对象集合到达。所以f和h对象都是非活动对象,可以被垃圾收集器回收。
那么在这个根集合(Root GC)中包括什么呢?虽然根对象集合和JVM的具体时间有关系,但是大都会包含如下一些元素:
- 在方法中局部变量区的对象的引用:如在方法中定义的对象的引用(在方法中定义并创建的对象的引用)就是根对象集合中的一个根对象,这些根对象直接存储在栈帧的局部变量区中。
- 在Java操作栈中的对象引用:有些对象是直接在操作栈中持有的,所以操作站肯定也包含根对象集合。
- 在常量池中的对象的引用:每个类后悔包含一个常量池,这些常量池中也会包含很多对象引用,如表示类名的字符串就保存在堆中,那么常量池中就只会持有这个字符串对象的引用。
- 在本地方法中持有的对象引用:有些方法被传入本地方法中,但是这些对象还没有被释放。
- 类的Class对象:当每个类被JVM加载时都会创建一个代表这个类的唯一数据类型的Class对象,而这Class对也同样会放在堆中,当这个类不再使用时,在方法区中的类数据和这个Class对象也同样需要回收。
JVM在做垃圾回收时会检查堆中的所有对象是否会被这些根对象(根对象集合)直接或者间接引用看,能够被引用的对象就是活动对象,否则就可以被垃圾收集器回收。
二、基于分代的垃圾回收算法
分代,顾名思义就是把堆内存分为不同的"时代",即按照不同的“年龄”来保存对象。 把对象按照寿命的长短分组,分为老年代和年轻代,新建的对象分在年轻代,如果对象经过几次回收后依然存活,那么再把这个对象放到老年代,老年代的收集频度不像年轻代那么频繁,从而提高了效率。它是hotspot(Sun JDK和OpenJDK中所带的虚拟机)中所使用的的垃圾收集方式。
这种设计思路是把堆划分为若干个子堆,每个堆对应一个年龄代,如下图所示:
JVM将整个堆划分为Young区、Old区、Perm区。分别存放不同的对象,三个区存放对象的区别:
-
Young区分为Eden区和Survivor区,其中所有新加载的对象都存放在Eden区中,当Eden区满时会触发Minor GC 将Eden区中依然存活的对象复制到Survivor的其中一个区中,并将Survivor另一个区中存活的对象复制到这个区中,以保证Survivor中始终有一个区是空的。
-
Old区存放的是Young区Survivor满后出发minor GC后仍然存活的对象。Eden满后会将对象存储到Survivor中,如果Survivor也存放不下这些对象,那这些对象可以直接存放到Old区中。如果Survivor中的对象足够老,也可以直接存放到Old区中。如果Old区满了,会触发Full GC,回收整个堆内存。
-
Perm区存放用来存放类的Class对象,如果一个类被频繁加载,也可能会导致Perm区满,Perm区的GC也是由Full GC触发的。
Sun对堆中的不同堆得大小也给出了建议,一般建议Young区堆大小为整个堆大小的1/4,而Young区中Survivor大小为整个Young区的1/8.
GC收集器对这些区采用的垃圾收集算法也不一样,Hotspot提供了三类垃圾收集算法:
- Serial Collector
- Parallel Collector
- CMS Collector