Java基础知识—垃圾回收机制

来源:互联网 发布:nginx listen 编辑:程序博客网 时间:2024/06/05 12:41

垃圾回收机制(Garbage Collection ,GC)是Java语言的核心技术之一。在java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给JVM(java虚拟机)来处理。那么在java中,

(1)哪些内存需要垃圾回收器回收?

(2)什么样的对象被认定为“垃圾”?

(3)这些对象被确定为垃圾后,又应该采用什么样的策略来进行回收?

(4)目前典型的垃圾收集器又有哪些?

(5)java GC的工作原理又是什么样子的?

一、哪些内存需要被垃圾回收期回收?

Java的堆是一个运行时数据区,类的实例(对象)从中分配空间。Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,这些对象通过new、newarray、anewarray和multianewarray等指令建立,但是它们不需要程序代码来显式地释放。一般来说,堆的是由垃圾回收 来负责的,尽管JVM规范并不要求特殊的垃圾回收技术,甚至根本就不需要垃圾回收,但是由于内存的有限性,JVM在实现的时候都有一个由垃圾回收所管理的堆。垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。 


二、如何确定某个对象是“垃圾”?

在确定某个对象是垃圾前,我们首先得明白该用什么方法去判断一个对象是垃圾?

1.引用计数算法

给堆中的对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;引用失效时,计数器就减1;任何时刻计数器为0的对象就不可能再被使用了。该算法实现简单,判定效率也很高,但是这个算法很难解决对象之间的相互循环引用的问题。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.

2.可达性分析算法

在主流的商用程序语言的主流实现中,都是使用这个算法来判定一个对象是否是存活的。这个算法的基本思想就是通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明这个对象是不可用的。如下图所示,蓝色表示的是存活的对象,白色表示的可被回收的对象。


在java语言中,可以作为GC Roots的对象包括下面几种:

(1)虚拟机栈(栈帧中的本地变量表)中引用的对象;

(2)方法区中类的静态属性引用的对象;

(3)方法区中常量引用的对象;

(4)本地方法栈JNI引用的对象。


三、典型的垃圾收集算法

1.标记清除算法

它是最基础的收集算法,如它的名字所说,算法分为"标记"和“清除”两个阶段:首先标记处所有需要回收的对象,在标记完成后统一回收所有被标记的对象。执行过程如下图:


但是,该算法有两个不足之处:一个是效率问题,标记和清除两个过程的效率都不高另一个是空间问题,标记清除之后产生大量不连续的内存碎片,这会导致以后在程序运行过程中需要分配较大对象时,无法找到足够连续内存。


2.复制算法

为了解决之前算法中提到的效率问题,提出了复制算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另外一块上面,然后再把自己使用过的内存空间一次性清理掉。执行过程如下图:


这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但是这种算法的代价是将内存缩小到原来的一半,代价有点高。


3.标记—整理算法

复制算法在对象存活率比较高时就要进行较多的复制操作,效率会较低。所以根据老年代的特点(存活率比较高),一般不会选择这种算法。有人提出了一种“标记—清理”算法,标记过程和“标记—清除”算法一样,但是后续不是直接对可回收对象进行清理,而是让所有的存活对象都向一端移动,然后直接清理掉段边界以外的内存。执行过程如下图:


4.分代收集算法

在当前商业虚拟机的垃圾收集都采用“分代收集”算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代,然后根据不同年代的特点采用适合的算法。比如说,在新生代,每次垃圾回收时都会有大批的对象死去,只有少量存活,那就选用“复制算法”;而老年代存活率比较高,可以选择 “标记—整理”算法。



四、典型的垃圾收集器

垃圾收集算法是 内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。下面介绍一下HotSpot(JDK 7)虚拟机提供的几种垃圾收集器,用户可以根据自己的需求组合出各个年代使用的收集器。

1.Serial/Serial Old

 Serial/Serial Old收集器是最基本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用的是标记—整理算法。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

2.ParNew
ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集。

3.Parallel Scavenge
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。

4.Parallel Old
       Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和标记—整理算法。

5.CMS
      CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是标记—清除算法。

6.G1
     G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。


五、详解Java GC的工作原理

1. 首先来看一下JVM内存结构,它是由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示。

JVM内存组成结构

1)堆

所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,结构图如下所示:

JVM内存结构之堆

新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象

2)栈

每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果

3)本地方法栈

用于支持native方法的执行,存储了每个native方法调用的状态

4)方法区

存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用持久代(PermanetGeneration)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。介绍完了JVM内存组成结构,下面我们再来看一下JVM垃圾回收机制。

2. JVM垃圾回收机制

JVM分别对新生代和旧生代采用不同的垃圾回收机制

新生代的GC:

新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代,

用javavisualVM来查看,能明显观察到新生代满了后,会把对象转移到旧生代,然后清空继续装载,当旧生代也满了后,就会报outofmemory的异常,如下图所示:

outofmemory的异常

在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)

1)串行GC

在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定

2)并行回收GC

在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数

3)并行GC

与旧生代的并发GC配合使用

旧生代的GC:

旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。在执行机制上JVM提供了串行GC(SerialMSC)、并行GC(parallelMSC)和并发GC(CMS),具体算法细节还有待进一步深入研究。

以上各种GC机制是需要组合使用的,指定方式由下表所示:

GC机制组合使用



参考:JVM垃圾回收机制和内存分配     详细介绍java垃圾回收机制  《深入理解Java虚拟机》










0 0
原创粉丝点击