详细介绍Java垃圾回收机制html
垃圾收集GC(Garbage Collection)是Java语言的核心技术之一,以前咱们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部运行机制上看,Java的垃圾回收原理与机制并未改变。垃圾收集的目的在于清除再也不使用的对象。GC经过肯定对象是否被活动对象引用来肯定是否收集该对象。GC首先要判断该对象是不是时候能够收集。两种经常使用的方法是引用计数和对象引用遍历。java
引用计数收集器程序员
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每一个对象(不是引用)都有一个引用计数。当一个对象被建立时,且将该对象分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象+1),但当一个对象的某个引用超过了生命周期或者被设置为一个新值时,对象的引用计数减1。任何引用计数为0的对象能够被看成垃圾收集。当一个对象被垃圾收集时,它引用的任何对象计数减1。web
这儿个人理解是:算法
A a=new A(); //(此时这儿的 new A(); 是对象一,a为引用,这时间引用计数为1 ) 编程
A b=a; //(这时间 引用计数为 1+1=2)浏览器
b=new A();//(此时这儿的 new A(); 是对象二,对象二的引用计数为1,b为引用,这时间引用计数为1--以前对象一的引用计数为 2-1=1 ) 数据结构
a=b;//(此时 对象二的引用计数为 1+1=2,而对象一的引用技术为 1-1=0--等待回收)多线程
上面的理解不知道对不对,如有错误请指正--上面加粗红色的这句话不懂--究竟是被回收的对象的引用对象计数减一,仍是 对象的引用计数由0变为-1并发
优势:引用计数收集器能够很快的执行,交织在程序运行中。对程序不被长时间打断的实时环境比较有利。
缺点: 没法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
跟踪收集器
早期的JVM使用引用计数,如今大多数JVM采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条连接,递归肯定可到达(reachable)的对象。若是某对象不能从这些根对象的一个(至少一个)到达,则将它做为垃圾收集。在对象遍历阶段,GC必须记住哪些对象能够到达,以便删除不可到达的对象,这称为标记(marking)对象。
下一步,GC要删除不可到达的对象。删除时,有些GC只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫作清除(sweeping)。这种方法的问题在于内存会分红好多小段,而它们不足以用于新的对象,可是组合起来却很大。所以,许多GC能够从新组织内存中的对象,并进行压缩(compact),造成可利用的空间。
为此,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内存结构,它是由堆、栈、本地方法栈、方法区等部分组成,结构图以下所示。
1)堆
全部经过new建立的对象的内存都在堆中分配,其大小能够经过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,结构图以下所示:
新生代。新建的对象都是用新生代分配内存,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的异常,以下图所示:
在执行机制上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机制是须要组合使用的,指定方式由下表所示:
ava提供finalize()方法,垃圾回收器准备释放内存的时候,会先调用finalize()。
(1).对象不必定会被回收。
(2).垃圾回收不是析构函数。
(3).垃圾回收只与内存有关。
(4).垃圾回收和finalize()都是靠不住的,只要JVM尚未快到耗尽内存的地步,它是不会浪费时间进行垃圾回收的。
有时当撤消一个对象时,须要完成一些操做。例如,若是一个对象正在处理的是非Java 资源,如文件句柄或window 字符字体,这时你要确认在一个对象被撤消之前要保证这些资源被释放。为处理这样的情况,Java 提供了被称为收尾(finalization )的机制。使用该机制你能够定义一些特殊的操做,这些操做在一个对象将要被垃圾回收程序释放时执行。
要给一个类增长收尾(finalizer ),你只要定义finalize ( ) 方法便可。Java 回收该类的一个对象时,就会调用这个方法。在finalize ( )方法中,你要指定在一个对象被撤消前必须执行的操做。垃圾回收周期性地运行,检查对象再也不被运行状态引用或间接地经过其余对象引用。就在对象被释放以前,Java 运行系统调用该对象的finalize( ) 方法。
finalize()方法的通用格式以下:
protected void finalize( )
{
// finalization code here
}
其中,关键字protected是防止在该类以外定义的代码访问finalize()标识符。该标识符和其余标识符将在第7章中解释。
理解finalize( ) 正好在垃圾回收之前被调用很是重要。例如当一个对象超出了它的做用域时,finalize( ) 并不被调用。这意味着你不可能知道什么时候——甚至是否——finalize( ) 被调用。所以,你的程序应该提供其余的方法来释放由对象使用的系统资源,而不能依靠finalize( ) 来完成程序的正常操做。
注意:若是你熟悉C ,那你知道C 容许你为一个类定义一个撤消函数(destructor ),它在对象正好出做用域以前被调用。Java不支持这个想法也不提供撤消函数。finalize() 方法只和撤消函数的功能接近。当你对Java 有丰富经验时,你将看到由于Java使用垃圾回收子系统,几乎没有必要使用撤消函数。
by Tim Gooch
在许多方面,Java 相似于 C++。Java 的语法很是相似于 C++,Java 有类、方法和数据成员;Java 的类有构造函数; Java 有异常处理。
可是,若是你使用过 C++ 会发现 Java 也丢掉一些多是你熟悉的特性。这些特性之一就是析构函数。取代使用析构函数,Java 支持finalize() 方法。
在本文中,咱们将描述 finalize() 与 C++ 析构函数的区别。另外,咱们将建立一个简单的 Applet 来演示 finalize() 是如何工做的。
最终的界限
与 Java 不一样,C++ 支持局部对象(基于栈)和全局对象(基于堆)。由于这一双重支持,C++ 也提供了自动构造和析构,这致使了对构造函数和析构函数的调用,(对于堆对象)就是内存的分配和释放。
在 Java 中,全部对象都驻留在堆内存,所以局部对象就不存在。结果,Java 的设计者以为不须要析构函数(象 C++ 中所实现的)。
取而代之,Java 定义了一个特殊的方法叫作finalize() ,它提供了 C++ 析构函数的一些功能。可是,finalize() 并不彻底与 C++ 的析构函数同样,并能够假设它会致使一系列的问题。finalize() 方法做用的一个关键元素是 Java 的垃圾回收器。
垃圾回收器
在 C/C++、Pascal和其余几种多种用途的编程语言中,开发者有责任在内存管理上发挥积极的做用。例如,若是你为一个对象或数据结构分配了内存,那么当你再也不使用它时必须释放掉该内存。
在 Java 中,当你建立一个对象时,Java 虚拟机(JVM)为该对象分配内存、调用构造函数并开始跟踪你使用的对象。当你中止使用一个对象(就是说,当没有对该对象有效的引用时),JVM 经过垃圾回收器将该对象标记为释放状态。
当垃圾回收器将要释放一个对象的内存时,它调用该对象的finalize() 方法(若是该对象定义了此方法)。垃圾回收器以独立的低优先级的方式运行,只有当其余线程挂起等待该内存释放的状况出现时,它才开始运行释放对象的内存。(事实上,你能够调用System.gc() 方法强制垃圾回收器来释放这些对象的内存。)
在以上的描述中,有一些重要的事情须要注意。首先,只有当垃圾回收器释放该对象的内存时,才会执行finalize()。若是在 Applet 或应用程序退出以前垃圾回收器没有释放内存,垃圾回收器将不会调用finalize()。
其次,除非垃圾回收器认为你的 Applet 或应用程序须要额外的内存,不然它不会试图释放再也不使用的对象的内存。换句话说,这是彻底可能的:一个 Applet 给少许的对象分配内存,没有形成严重的内存需求,因而垃圾回收器没有释放这些对象的内存就退出了。
显然,若是你为某个对象定义了finalize() 方法,JVM 可能不会调用它,由于垃圾回收器未曾释放过那些对象的内存。调用System.gc() 也不会起做用,由于它仅仅是给 JVM 一个建议而不是命令。
finalize() 有什么优势呢?
若是finalize() 不是析构函数,JVM 不必定会调用它,你可能会疑惑它是否在任何状况下都有好处。事实上,在 Java 1.0 中它并无太多的优势。
根据 Java 文档,finalize() 是一个用于释放非 Java 资源的方法。可是,JVM 有很大的可能不调用对象的finalize() 方法,所以很难证实使用该方法释放资源是有效的。
Java 1.1 经过提供一个System.runFinalizersOnExit() 方法部分地解决了这个问题。(不要将这个方法与 Java 1.0 中的System.runFinalizations() 方法相混淆。)不象System.gc() 方法那样,System.runFinalizersOnExit() 方法并不当即试图启动垃圾回收器。而是当应用程序或 Applet 退出时,它调用每一个对象的finalize() 方法。
正如你可能猜想的那样,经过调用System.runFinalizersOnExit() 方法强制垃圾回收器清除全部独立对象的内存,当清除代码执行时可能会引发明显的延迟。如今创建一个示例 Applet 来演示 Java 垃圾回收器和finalize() 方法是如何相互做用的。
回收垃圾
经过使用Java Applet Wizard 建立一个新的 Applet 开始。当提示这样作时,输入final_things做为 Applet 名,并选择不要生成源文件注释。
接下来,在Java Applet Wizard 进行第三步,不要选择多线程选项。在第五步以前,根据须要修改 Applet 的描述。
当你单击Finish 后,Applet Wizard 将生成一个新的工做空间,并为该项目建立缺省的 Java 文件。从列表 A 中选择适当的代码输入(咱们已经突出显示了你须要输入的代码)。
当你完成代码的输入后,配置Internet 浏览器将System.out 的输出信息写到Javalog.txt 文件中。(在IE 选项对话框的高级页面中选择起用 Java Logging。)
编译并运行该 Applet。而后,等待 Applet 运行(你将在状态栏中看到 Applet 已启动的信息),退出浏览器,并打开Javalog.txt 文件。你将会发现相似于下列行的信息:
1000 things constructed
0 things finalized
正如你可以看到的那样,创建了1,000个对象仍然没有迫使垃圾回收器开始回收空间,即便在 Applet 退出时也没有对象被使用。
如今,删除在stop() 方法第一行中的注释符以起用System.gc() 方法。再次编译并运行该 Applet ,等待 Applet 完成运行,并退出浏览器。当你再次打开Javalog.txt 文件,你将看到下列行:
1000 things constructed
963 things finalized
此次,垃圾回收器认为大多数对象未被使用,并将它们回收。按顺序,当垃圾回收器开始释放这些对象的内存时,JVM 调用它们的finalize() 方法。
继承finalize()?
顺便,若是你在类中定义了finalize() ,它将不会自动调用基类中的方法。在咱们讨论了finalize() 与 C++ 的析构函数的不一样点后,对这个结论不会惊讶,由于为某个类定制的清除代码另外一个类不必定会须要。
若是你决定要经过派生一个类的finalize() 方法来调用基类中的finalize() 方法,你能够象其余继承方法同样处理。
protected void finalize()
{
super.finalize();
// other finalization code...
}
除了容许你控制是否执行清除操做外,这个技术还使你能够控制当前类的finalize() 方法什么时候执行。
结论
然而有益的是,Java 的自动垃圾回收器不会失去平衡。做为便利的代价,你不得不放弃对系统资源释放的控制。不象 C++ 中的析构函数,Java Applet 不会自动执行你的类中的finalize() 方法。事实上,若是你正在使用 Java 1.0,即便你试图强制它调用finalize() 方法,也不能确保将调用它。
所以,你不该当依靠finalize() 来执行你的 Applet 和应用程序的资源清除工做。取而代之,你应当明确的清除那些资源或建立一个try...finally 块(或相似的机制)来实现。
finalize方法是与Java编程中的垃圾回收器有关系。即:当一个对象变成一个垃圾对象的时候,若是此对象的内存被回收,那么就能够调用系统中定义的finalize方法来完成
固然,Java的内存回收能够由JVM来自动完成。若是你手动使用,则可使用上面的方法。
举例说明:
[java] view plaincopyprint?
<EMBED id=ZeroClipboardMovie_1 name=ZeroClipboardMovie_1 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=19 width=40 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=1&width=40&height=19" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
public class FinalizationDemo {
public static void main(String[] args) {
Cake c1 = new Cake(1);
Cake c2 = new Cake(2);
Cake c3 = new Cake(3);
c2 = c3 = null;
System.gc(); //Invoke the Java garbage collector
}
}
class Cake extends Object {
private int id;
public Cake(int id) {
this.id = id;
System.out.println("Cake Object " + id + "is created");
}
protected void finalize() throws java.lang.Throwable {
super.finalize();
System.out.println("Cake Object " + id + "is disposed");
}
}
结果运行:
[java] view plaincopyprint?
<EMBED id=ZeroClipboardMovie_2 name=ZeroClipboardMovie_2 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=19 width=40 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=2&width=40&height=19" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
C:\1>java FinalizationDemo
Cake Object 1is created
Cake Object 2is created
Cake Object 3is created
Cake Object 3is disposed
Cake Object 2is disposed
final
修饰符(关键字)若是一个类被声明为final,意味着它不能再派生出新的子类,不能做为父类被继承。所以一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,能够保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在之后的引用中只能读取,不可修改。被声明为final的方法也一样只能使用,不能重载。
finally
异常处理时提供 finally 块来执行任何清除操做。若是抛出一个异常,那么相匹配的 catch 子句就会执行,而后控制就会进入 finally 块(若是有的话)。通常异常处理块须要。
finalize
方法名。Java 技术容许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去以前作必要的清理工做。这个方法是由垃圾收集器在肯定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,所以全部的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其余清理工做。finalize() 方法是在垃圾收集器删除对象以前对这个对象调用的。
Java中全部类都从Object类中继承finalize()方法。
当垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法。值得C++程序员注意的是,finalize()方法并不能等同与析构函数。Java中是没有析构函数的。C++的析构函数是在对象消亡时运行的。因为C++没有垃圾回收,对象空间手动回收,因此一旦对象用不到时,程序员就应当把它delete()掉。因此析构函数中常常作一些文件保存之类的收尾工做。可是在Java中很不幸,若是内存老是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然期望它作收尾工做是靠不住的。
那么finalize()到底是作什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java程序有垃圾回收器,因此通常状况下内存问题不用程序员操心。但有一种JNI(Java Native Interface)调用non-Java程序(C或C++),finalize()的工做就是回收这部分的内存。