java内存分配与垃圾回收
引用:http://www.cnblogs.com/boothsun/p/8120776.html
JVM内存管理机制说到底就是为了解决两个问题:给对象分配内存以及回收分配给对象的内存。
一、内存分配
堆被分为两部分:Young Generation 和 Old Gereration。
Young Gen被分成三部分:Eden Memory和两个Survivor Memory(比例 8:1:1)
1、新生代Young Generation
- 多数情况下,对象都在新生代Eden区中分配,但一些大对象可能会直接进入到老年代。
- Eden满了怎么办?JVM就会执行Minor GC。被引用的对象都会存活下来,它们将被移到Survivor区域里,也就是 图中的S0或S1。
- 同一时间的两个Survivor区,一个用来保存对象,另一个是空的;每次进行Minor GC垃圾回收时,就把Eden,From的可达对象复制到To区域中,一些生存时间长的就复制到老年代,接着就清除Eden,From空间,最后把原来的To空间变为From空间,原来的From空间变为To空间。( 有点类似于双缓冲队列 )
- 虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并进过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。对象在Survivro区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数
-XX:MaxTenuringThreshold
来设置。 - 动态对象年龄判定:为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
- Young区域大部分对象 朝生夕灭,因此Minor GC回收频率高且回收速度很快。
- 新生代采用 复制算法 。
2、老年代Old Gereration
- 回收机制:采用标记压缩算法回收垃圾。
- 回收频率:因为很少对象会死掉,所以执行频率不高,而且需要较长时间来完成。
- 对象来源:
- 大对象直接进入老年代(虚拟机提供了一个
-XX:PretenureSizeThreshold
参数,令大于这个设置值的对象直接进老年代分配,这样做的目的是避免在Eden区以及两个Survivor区之间发生大量的内存拷贝。) - Young代中生存时间长的可达对象
二、内存回收
1、如何判断java对象需要被回收?GC判断方法
- 引用计数:记录每一个对象被其它对象所持有的引用数,为0可回收(会有循环引用的问题)
- 可达性分析算法:当一个对象不再被任何的GC root对象引用链相连时说明该对象不再可用;
GC root对象:方法区中常量和静态变量引用的对象,虚拟机栈中变量引用的对象,本地方法栈中引用的对象;
java对象的四种引用:
1.强引用 :创建一个对象并把这个对象直接赋给一个变量,eg :Person person = new Person(“sunny”); 不管系统资源有么的紧张,强引用的对象都绝对不会被回收,即使他以后不会再用到。
2.软引用 :通过SoftReference类实现,eg
: SoftReference p = new SoftReference(new
Person(“Rain”));内存非常紧张的时候会被回收,其他时候不会被回收,所以在使用之前要判断是否为null从而判断他是否已经被回收了。
3.弱引用 :通过WeakReference类实现,eg : WeakReference p = new WeakReference(new Person(“Rain”));不管内存是否足够,系统垃圾回收时必定会回收
4.虚引用 :不能单独使用,主要是用于追踪对象被垃圾回收的状态,为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。通过PhantomReference类和引用队列ReferenceQueue类联合使用实现
2、垃圾回收算法
- 停止-复制算法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,内存浪费严重。它先暂停程序的运行,然后将所有存活的对象从当前堆复制到另外一个堆。此方法耗费空间且效率低,适用于存活对象少。 - 标记-清扫算法
遍历去标记所有需要回收的对象,标记结束之后,开始清理工作,被标记的对象都会被释放掉,会产生大量内存碎片。 -
标记-整理
先标记需要回收的对象,将存活对象向内存区域的一端移动,然后清理掉端以外的内存。适用于存活对象多。 -
分代算法
在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用停止复制算法来完成收集,而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。
3、垃圾回收器
- Serial收集器:一个单线程的新生代收集器,它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。简单高效
- Parallel(并行)收集器:JVM缺省收集器,其最大的优点是使用多个线程来通过扫描并压缩堆。而并行收集器可以理解是多线程串行收集,在串行收集基础上采用多线程方式进行GC,很好的弥补了串行收集的不足,可以大幅缩短停顿时间,因此对于空间不大的区域(如young generation),采用并行收集器停顿时间很短,回收效率高,适合高频率执行。
- CMS收集器:基于“标记-清除”算法实现的,它使用多线程的算法去扫描老年代堆(标记)并对发现的待回收对象进行回收(清除),容易产生大量内存碎片使得大对象无法创建然后不得不提前触发full GC。CPU资源占用过大,标记之后容易产生浮动垃圾只能留到下一次GC处理
- G1收集器:G1收集器是基于“标记-整理”算法实现的收集器,也就是说它不会产生空间碎片。G1是一个针对多处理器大容量内存的服务器端的垃圾收集器,其目标是在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。它可以非常精确地控制停顿,既能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,具备了一些实时Java(RTSJ)的垃圾收集器的特征。
4、finalize()方法
finalize()方法是对象逃脱死亡命运的最后一次机会。
如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
注意:finalize()方法只能被系统调用一次,如果这次对象逃逸成功,下一次再次被回收时候就无法自救了。
5、永久代的回收
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
判定一个类是否是“无用的类”的条件有三个:
1,类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
2,加载该类的ClassLoader已经被回收。
3,该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。