Java垃圾回收机制

之前没有写博客的习惯,学过的东西又不能一直都记着,因此用到“方现查”,每次都要看不少东西,才能把某一方面的东西看全。html

对于Java垃圾回收机制,这个好久前就学过,而且理解过了,好长时间不用,就丢到回爪洼岛了,这里仍是记下,方便之后再次查看,不用浪费太多时间。java

了解Java垃圾回收机制,就要知道Java各个版本的区别,尤为是随着JDK版本的提高,都比较之前版本有哪些改进。最近,尤为是JDK1.7中加入了G1,这个是增长的新的回收方式,起始在JDK1.6 40左右的版本的时候就已经加入实验性的G1了。程序员

线面是我转发的博文,本身写没那么多时间,有两篇不错,两篇各有互补点。算法

第一篇:说的比较多,但不是太详细

http://www.cnblogs.com/laoyangHJ/articles/java_gc.html数组

垃圾收集GC(Garbage Collection)是Java语言的核心技术之一,以前咱们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部运行机制上看,Java的垃圾回收原理与机制并未改变。垃圾收集的目的在于清除再也不使用的对象。GC经过肯定对象是否被活动对象引用来肯定是否收集该对象。GC首先要判断该对象是不是时候能够收集。两种经常使用的方法是引用计数和对象引用遍历。多线程

总的来讲,可分为两大类

引用计数收集器并发

引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每一个对象(不是引用)都有一个引用计数。当一个对象被建立时,且将该对象分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象+1),但当一个对象的某个引用超过了生命周期或者被设置为一个新值时,对象的引用计数减1。任何引用计数为0的对象能够被看成垃圾收集。当一个对象被垃圾收集时,它引用的任何对象计数减1。性能

优势:引用计数收集器能够很快的执行,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。优化

缺点: 没法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.spa

跟踪收集器

早期的JVM使用引用计数,如今大多数JVM采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条连接,递归肯定可到达(reachable)的对象。若是某对象不能从这些根对象的一个(至少一个)到达,则将它做为垃圾收集。在对象遍历阶段,GC必须记住哪些对象能够到达,以便删除不可到达的对象,这称为标记(marking)对象。

下一步,GC要删除不可到达的对象。删除时,有些GC只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫作清除(sweeping)。这种方法的问题在于内存会分红好多小段,而它们不足以用于新的对象,可是组合起来却很大。所以,许多GC能够从新组织内存中的对象,并进行压缩(compact),造成可利用的空间。

为此,GC须要中止其余的活动活动。这种方法意味着全部与应用程序相关的工做中止,只有GC运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的 GC不断增长或同时运行以减小或者清除应用程序的中断。有的GC使用单线程完成这项工做,有的则采用多线程以增长效率。

实际上GC判断对象是否可达看的是强引用。更准确的描述是,一个对象存在强引用,一定是从其它强引用对象的本地变量,静态变量或者其它相似的地方直接引用过来的。换句话说,若是一堆对象经过某个不存活的对象“强引用”过来的话,它们会被一块儿回收掉。

一些具体经常使用的垃圾收集算法

(1)标记-清除

这种收集器首先遍历对象图并标记可到达的对象,而后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器通常使用单线程工做并中止其余操做。而且,因为它只是清除了那些未标记的对象,而并无对标记对象进行压缩,致使会产生大量内存碎片,从而浪费内存。

(2)标记-压缩

有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也中止其余操做。

(3)复制

这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,JVM生成的新对象则放在另外一半空间中。GC运行时,它把可到达对象复制到另外一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则致使效率下降。而且对于指定大小堆来讲,须要两倍大小的内存,由于任什么时候候都只使用其中的一半。

 (4) 增量收集器

增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾,也可理解为把堆栈分红一小块一小块,每次仅对某一个块进行垃圾收集。这会形成较小的应用程序中断时间,使得用户通常不能觉察到垃圾收集器正在工做。

(5)分代

复制收集器的缺点是:每次收集时,全部的标记对象都要被拷贝,从而致使一些生命周期很长的对象被来回拷贝屡次,消耗大量的时间。而分代收集器则可解决这个问题,分代收集器把堆栈分为两个或多个域,用以存放不一样寿命的对象。JVM生成的新对象通常放在其中的某个域中。过一段时间,继续存在的对象(非短命对象)将得到使用期并转入更长寿命的域中。分代收集器对不一样的域使用不一样的算法以优化性能。

并行收集器

并行收集器使用某种传统的算法并使用多线程并行的执行它们的工做。在多CPU机器上使用多线程技术能够显著的提升java应用程序的可扩展性。

最后,贴出一个很是简单的跟踪收集器的例图,以便你们加深对收集器的理解:

跟踪收集器的例图

使用垃圾收集器要注意的地方

下面将提出一些有关垃圾收集器要注意的地方,垃圾收集器知识不少,下面只列出一部分必要的知识:

(1)每一个对象只能调用finalize( )方法一次。若是在finalize( )方法执行时产生异常(exception),则该对象仍能够被垃圾收集器收集。

(2)垃圾收集器跟踪每个对象,收集那些不可触及的对象(即该对象再也不被程序引用 了),回收其占有的内存空间。但在进行垃圾收集的时候,垃圾收集器会调用该对象的finalize( )方法(若是有)。若是在finalize()方法中,又使得该对象被程序引用(俗称复活了),则该对象就变成了可触及的对象,暂时不会被垃圾收集了。可是因为每一个对象只能调用一次finalize( )方法,因此每一个对象也只可能 "复活 "一次。

(3)Java语言容许程序员为任何方法添加finalize( )方法,该方法会在垃圾收集器交换回收对象以前被调用。但不要过度依赖该方法对系统资源进行回收和再利用,由于该方法调用后的执行结果是不可预知的。

(4)垃圾收集器不能够被强制执行,但程序员能够经过调研System.gc方法来建议执行垃圾收集。记住,只是建议。通常不建议本身写System.gc,由于会加大垃圾收集工做量。

详解Java GC的工做原理

概要: JVM内存结构由堆、栈、本地方法栈、方法区等部分组成,另外JVM分别对新生代和旧生代采用不一样的垃圾回收机制。

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机制组合使用

第二篇:详细介绍了垃圾回收机制的算法

转自:http://www.cnblogs.com/dolphin0520/p/3783345.html

说到垃圾回收(Garbage Collection,GC),不少人就会天然而然地把它和Java联系起来。在Java中,程序员不须要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。顾名思义,垃圾回收就是释放垃圾占用的空间,那么在Java中,什么样的对象会被认定为“垃圾”?那么当一些对象被肯定为垃圾以后,采用什么样的策略来进行回收(释放空间)?在目前的商业虚拟机中,有哪些典型的垃圾收集器?下面咱们就来逐一探讨这些问题。如下是本文的目录大纲:

  一.如何肯定某个对象是“垃圾”?

  二.典型的垃圾收集算法

  三.典型的垃圾收集器

一.如何肯定某个对象是“垃圾”?

  在这一小节咱们先了解一个最基本的问题:若是肯定某个对象是“垃圾”?既然垃圾收集器的任务是回收垃圾对象所占的空间供新的对象使用,那么垃圾收集器如何肯定某个对象是“垃圾”?—即经过什么方法判断一个对象能够被回收了。

  在java中是经过引用来和对象进行关联的,也就是说若是要操做对象,必须经过引用来进行。那么很显然一个简单的办法就是经过引用计数来判断一个对象是否能够被回收。不失通常性,若是一个对象没有任何引用与之关联,则说明该对象基本不太可能在其余地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。

  这种方式的特色是实现简单,并且效率较高,可是它没法解决循环引用的问题,所以在Java中并无采用这种方式(Python采用的是引用计数法)。看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public  class  Main {
     public  static  void  main(String[] args) {
         MyObject object1 = new  MyObject();
         MyObject object2 = new  MyObject();
         
         object1.object = object2;
         object2.object = object1;
         
         object1 = null ;
         object2 = null ;
     }
}
 
class  MyObject{
     public  Object object = null ;
}

  最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,可是因为它们互相引用对方,致使它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。

  为了解决这个问题,在Java中采起了 可达性分析法。该方法的基本思想是经过一系列的“GC Roots”对象做为起点进行搜索,若是在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被断定为不可达的对象不必定就会成为可回收对象。被断定为不可达的对象要成为可回收对象必须至少经历两次标记过程,若是在这两次标记过程当中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

  至于可达性分析法具体是如何操做的我暂时也没有看得很明白,若是有哪位朋友比较清楚的话请不吝指教。

  下面来看个例子:

1
2
3
4
5
6
7
Object aobj = new  Object ( ) ;
Object bobj = new  Object ( ) ;
Object cobj = new  Object ( ) ;
aobj = bobj;
aobj = cobj;
cobj = null ;
aobj = null ;

   第几行有可能会使得某个对象成为可回收对象?第7行的代码会致使有对象会成为可回收对象。至于为何留给读者本身思考。

  再看一个例子:

1
2
3
String str = new  String( "hello" );
SoftReference<String> sr = new  SoftReference<String>( new  String( "java" ));
WeakReference<String> wr = new  WeakReference<String>( new  String( "world" ));

  这三句哪句会使得String对象成为可回收对象?第2句和第3句,第2句在内存不足的状况下会将String对象断定为可回收对象,第3句不管什么状况下String对象都会被断定为可回收对象。

  最后总结一下日常遇到的比较常见的将对象断定为可回收对象的状况:

  1)显示地将某个引用赋值为null或者将已经指向某个对象的引用指向新的对象,好比下面的代码:

1
2
3
4
5
Object obj = new  Object();
obj = null ;
Object obj1 = new  Object();
Object obj2 = new  Object();
obj1 = obj2;

    2)局部引用所指向的对象,好比下面这段代码:

1
2
3
4
5
6
7
8
void  fun() {
 
.....
     for ( int  i= 0 ;i< 10 ;i++) {
         Object obj = new  Object();
         System.out.println(obj.getClass());
     }   
}

   循环每执行完一次,生成的Object对象都会成为可回收的对象。

  3)只有弱引用与其关联的对象,好比:

1
WeakReference<String> wr = new  WeakReference<String>( new  String( "world" ));

二.典型的垃圾收集算法

  在肯定了哪些垃圾能够被回收后,垃圾收集器要作的事情就是开始进行垃圾回收,可是这里面涉及到一个问题是:如何高效地进行垃圾回收。因为Java虚拟机规范并无对如何实现垃圾收集器作出明确的规定,所以各个厂商的虚拟机能够采用不一样的方式来实现垃圾收集器,因此在此只讨论几种常见的垃圾收集算法的核心思想。

  1.Mark-Sweep(标记-清除)算法

  这是最基础的垃圾回收算法,之因此说它是最基础的是由于它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出全部须要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程以下图所示:

  从图中能够很容易看出标记-清除算法实现起来比较容易,可是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会致使后续过程当中须要为大对象分配空间时没法找到足够的空间而提早触发新的一次垃圾收集动做。

  2.Copying(复制)算法

  为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程以下图所示:

  这种算法虽然实现简单,运行高效且不容易产生内存碎片,可是却对内存空间的使用作出了高昂的代价,由于可以使用的内存缩减到原来的一半。

  很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,若是存活对象不少,那么Copying算法的效率将会大大下降。

  3.Mark-Compact(标记-整理)算法

  为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep同样,可是在完成标记以后,它不是直接清理可回收对象,而是将存活对象都向一端移动,而后清理掉端边界之外的内存。具体过程以下图所示:

  

  4.Generational Collection(分代收集)算法

  分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不一样的区域。通常状况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特色是每次垃圾收集时只有少许对象须要被回收,而新生代的特色是每次垃圾回收时都有大量的对象须要被回收,那么就能够根据不一样代的特色采起最适合的收集算法。

  目前大部分垃圾收集器对于新生代都采起Copying算法,由于新生代中每次垃圾回收都要回收大部分对象,也就是说须要复制的操做次数较少,可是实际中并非按照1:1的比例来划分新生代的空间的,通常来讲是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另外一块Survivor空间中,而后清理掉Eden和刚才使用过的Survivor空间。

  而因为老年代的特色是每次回收都只回收少许对象,通常使用的是Mark-Compact算法。

  注意,在堆区以外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部份内容:废弃常量和无用的类。

三.典型的垃圾收集器

  垃圾收集算法是 内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。下面介绍一下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、多核环境。所以它是一款并行与并发收集器,而且它能创建可预测的停顿时间模型。

  下面补充一下关于内存分配方面的东西:

  

  对象的内存分配,往大方向上讲就是在堆上分配,对象主要分配在新生代的Eden Space和From Space,少数状况下会直接分配在老年代。若是新生代的Eden Space和From Space的空间不足,则会发起一次GC,若是进行了GC以后,Eden Space和From Space可以容纳该对象就放在Eden Space和From Space。在GC的过程当中,会将Eden Space和From  Space中的存活对象移动到To Space,而后将Eden Space和From Space进行清理。若是在清理的过程当中,To Space没法足够来存储某个对象,就会将该对象移动到老年代中。在进行了GC以后,使用的即是Eden space和To Space了,下次GC时会将存活对象复制到From Space,如此反复循环。当对象在Survivor区躲过一次GC的话,其对象年龄便会加1,默认状况下,若是对象年龄达到15岁,就会移动到老年代中。

  通常来讲,大对象会被直接分配到老年代,所谓的大对象是指须要大量连续存储空间的对象,最多见的一种大对象就是大数组,好比:

  byte[] data = new byte[4*1024*1024]

  这种通常会直接在老年代分配存储空间。

  固然分配的规则并非百分之百固定的,这要取决于当前使用的是哪一种垃圾收集器组合和JVM的相关参数。

相关文章
相关标签/搜索