JAVA-GC
Posted formice
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA-GC相关的知识,希望对你有一定的参考价值。
JAVA-GC
1.如何确定某个对象是“垃圾”?
(1)引用计数法:引用计数很好理解,就是为每一个对象维护一个计数器,存储引用这个对象的个数,如:
A a = new A(); // new出来的这个对象“X”的引用就为1
A b = a ; // “X”引用+1
a = null; // “X”引用-1
当对象“X”的引用为0,说明没人再引用它,它就没用了。
(2)根路径查找法(GC Roots):如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
2.典型的垃圾收集算法
(1)Mark-Sweep(标记-清除)算法
这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:
从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
(2)Copying(复制)算法
为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示:
这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。
很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
(3)Mark-Compact(标记-整理)算法
为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:
(4)Generational Collection(分代收集)算法
如上图所示,为Java堆中的各代分布。
- Young(年轻代)复制算法
年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。 - Tenured(年老代)标记清除|标记整理
老年代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。 - Perm(持久代)很难发生GC
用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。 - OOM导致的原因:Full GC后,发现剩余的(heap,方法区)空间还是不够程序使用,这样就会导致OOM, 能导致Full GC:
- Tenured被写满
- Perm域被写满
- System.gc()被显示调用
- 上一次GC之后Heap的各域分配策略动态变化
3.典型的垃圾收集器
垃圾收集算法是 内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。下面介绍一下HotSpot(JDK 7)虚拟机提供的几种垃圾收集器,用户可以根据自己的需求组合出各个年代使用的收集器。
(1)Serial/Serial Old
Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。
(2)ParNew
ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。
(3)Parallel Scavenge
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。
(4)Parallel Old
Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。
(5)CMS
CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。
(6)G1
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。
4.JVM内存分配过程
(1)JVM 会试图为相关Java对象在Eden中初始化一块内存区域。
(2)当Eden空间足够时,内存申请结束;否则到下一步。
(3)JVM 试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收)。释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区。
(4)Survivor区被用来作为Eden及Old的中间交换区域,当Old区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区。
(5)当Old区空间不够时,JVM 会在Old区进行完全的垃圾收集(0级)。
(6)完全垃圾收集后,若Survivor及Old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory”错误。
5.什么时候发生GC
触发Minor Gc:
(1)Eden区内存不足时会触发Minor gc
触发Full Gc:
(1)老年代空间不足
(2)perm gen空间满
(3)CMS GC时出现promotion failed和concurrent mode failure
(4)统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间
以上是关于JAVA-GC的主要内容,如果未能解决你的问题,请参考以下文章