一、垃圾回收器和finalize() 

 java垃圾回收器只负责回收无用对象占据的内存资源。但是如果你的对象不是通过 new 创建的(所有的new 对象都往堆中开辟资源,在一个地方,方便清理/管理资源),它会不知道该如果释放该对象的这块特殊内存。为了应对这个情况,Object自带一个finalize()方法。

  finalize()这方法的原理是:一旦垃圾回收器准备释放该对象占用的存储空间,将会先调用其继承/重写的fialize(),并且调用方法后不是立即执行回收,而是在下一次(JVM觉得需要更大内存的时候)回收动作发生时,才会真正回收对象占用的内存。所以一般自己重写fialize()方法,是在回收的最后时刻做一些重要的清理工作。

java垃圾回收几个特点:

1、对象可能不被垃圾回收

   你创建的对象做了某个功能,比如显示在电脑的屏幕上。那么除非你特别处理从屏幕上擦除,它永远不可能得到清理。所以如果在finalize()方法中做擦除屏幕的处理,当垃圾回收时,finalize()被调用,屏幕图像清除。请注意:垃圾回收器只有在JVM觉得需要更大内存的时候才会运行(虽然开销小,但是一直运行还是有开销的),所以大部分回收动作是发生在濒临存储空间用完的那一刻,逼得JVM去运行垃圾回收器。如果程序执行结束 (或者中断运行),那些资源也会全部还给操作系统。

2、垃圾回收并不等于析构

  这个是C的概念,因为java和C的牵扯太深,所以经常拿来对比。简单说C有一个东西叫析构函数,在销毁对象前必须执行这个析构函数。这里的垃圾回收并不代表析构。finalize()就是类似功能但是不等于。

3、垃圾回收只与内存有关

  这里就要讲到finalize()的真正用途。该方法内部执行的操作也应该和内存及其回收有关,所以fialize()方法不是通用的方法。你可能会想到,当对象包含成员对象属性的时候,finalize()是否应该明确要清除那些对象呢?不正确。应该这样理解:无论对象如果创建,垃圾回收器都会负责释放对象占用的所有内存。所以finalize()一般是来处理通过创建对象以外的方式为对象分配存储空间。说起来有些绕口,但是举个例子就知道了。

  java跟踪源码的时候经常遇到关键字native修饰的方法,这些方法也叫“本地方法“。在使用这些本地方法的时候,内部调用的是非java代码的方式(不是C就是C++)。.这些非代码中,也许会用到C的malloc()函数系列来分配存储空间。这样除非调用C的free()方法,否则存储空间将不会释放。所以可以在finalize()使用native方式调用free。

  以上,就是建议尽量少重写finalize()的道理。

二、垃圾回收条件

  既然fialize()使用场景这么生僻,那就不要指望频繁使用fialize()。你必须创建其他的清理方法,来自己根据业务清理。但是fialize()有个特点是:程序调用它,是该对象“终结条件“的验证。也就是被标记了,该对象已死,可以回收了。例如:某个对象代表打开的一个文件,在对象被回收前程序员应该关闭文件。只要对象存在没有被适当清理的部分,程序就存在隐晦的缺陷。fialize()可以用来最终发现这种情况。


// Using finalize() to detect an object that
// hasn't been properly cleaned up.

class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
protected void finalize() {
if(checkedOut)
System.out.println("Error: checked out");
// Normally, you'll also do this:
// super.finalize(); // Call the base-class version
}
} public class TerminationCondition {
public static void main(String[] args) {
Book novel = new Book(true);
// Proper cleanup:
novel.checkIn();
// Drop the reference, forget to clean up:
new Book(true);
// Force garbage collection & finalization:
System.gc();
}
}/* Output:
Error: checked out
*///:~

这个例子的终结条件是:所有Book对象在被当做垃圾回收前都应该checkIn。但是在main里面,第二本书没有checkIn,这个时候通过finalize(),就能明确知道,有的对象没有在销毁前处理干净了。另外,代码中还使用了System.gc();这是强制唤起垃圾回收机器,来触发BOOK的finalize();当然,如果不这样强制唤起也行,当程序运行到被分配了大量内存的时候(可以大量反复创建BOOK),逼得垃圾回收器会自动触发。如果BOOK有继承某个父类,要触发该父类的finalize(),可以使用super.finalize();调用。

三、垃圾回收器如何工作

  一般印象里面,在堆内分配新资源会比较慢,毕竟比不了堆栈快。但是其实JVM在这方面是做了大量的优化,其中垃圾回收器对于提高对象的创建速度,具有明显效果。即使用垃圾回收器释放存储空间有利于未使用存储空间的分配。通俗点就是说,垃圾回收器回收的内存越多,创建对象理论上会更快(还是有临界的,一般认为媲美其他语言在堆栈中创建对象,比如C)。C的堆就好像一个院子,里面的每个对象各管各的存储空间。一段时间以后某个对象被销毁了。它的空间必须被重新使用。在某些JVM中,堆就像一个传送带,分配一个新的对象,它就往前移动一格。这个意味着空间分配会非常快(寻址快)。java的寻址指针只需要简单移动到尚未分配的区域就行,这样效率比得上C在堆栈上分配的速度。其中,记录对象空间地址“下标“方面,还是有部分开销的,但是比C需要查找堆的开销,小得多。其实,java中的堆未必完全是像传送带,因为这会造成频繁的内存页面调度(内存是分页的,翻页时是要移出硬盘,放在虚拟内存上)。页面调度会显著影响性能,最终,在创建足够的对象后,内存(大量虚拟内存充斥)资源耗尽。

  这里就轮到垃圾回收器登场了,当垃圾回收器工作的时候,一边回收空间,一边使堆中的对象排列紧凑。这样“堆指针”就可以很容易移动到更靠近传送带的开始处(java堆分配空间是先进),也就尽量避免了页面错误。垃圾回收期会对对象重新排列,实现高速的、有无限空间(?)可以分配的堆模型。

下面是垃圾回收(不止java)常用的三种设计方式:

1、引用计数

  每个对象含有一个应用计数。当有引用连接到对象的时候+1,引用离开作用域或者赋值null的时候-1。好了,那么当发现某个对象的引用是0的时候,就释放它占用的空间(这里会出现一变为0就释放空间)。这里就存在缺陷,如果对象循环引用,即A引用B,B引用A,就出现“对象可以被回收,但是引用计数不是0”的情况。对于垃圾回收器来说,定位这种互相引用的对象组开销极大。另外,管理引用计数的开销不大,但是这个会在整个程序生命周期内持续发生。引用计数的方式一般用来表述垃圾收集,但没有应用于任何一种JVM中。

2、stop-and-copy

  这种方式是先暂停程序(不是后台运行,而是停止程序,执行垃圾回收),然后将所以存活的对象从当前的堆中复制到另外一个堆,剩下的都是垃圾。当对象被复制到新的家(堆)时,会把这些对象一个挨着一个,所以新堆保持紧凑排列。这个就是前面说的JVM虚拟机的垃圾回收期为什么能做到使对象紧凑排列了。复制过程会产生新的开销,以及所有指向就旧对象的引用都要指向新的地址。这里可能出现来自非堆的引用(不是new出来的对象),这些会在遍历旧堆的引用的时候被找出来,重新指向新堆。

  这种回收方式,效率低。首先,是因为要有两个堆,然后对象要在两个堆中复制转移,所以实际上维护的空间比理论上大一倍。例如,某些JVM的做法是,在堆里面分配几个大的内存块,复制的操作在这里个内存块中进行。其次,当程序运行趋于稳定以后,产生的垃圾比较少,甚至可能没有垃圾。这个时候来回复制就很浪费了。为了避免浪费,JVM会进行检查,要是没有新垃圾产生,就自动转换成下一种模式。

3、mark-and-sweep

  Sun公司早期版本的虚拟机使用的就是这个。这种方式的思路是从堆和静态存储区出发,遍历所有的引用,然后就能找到所有存活对象。每当找到一个存活对象,就给该对象一个标记,记上一笔。所有标记都做完以后,清理开始。在清理的时候,没有标记的对象将被释放,不会发生复制动作。这个时候堆中就有点像C的样子,所以如果要让剩下的对象内存连续,就需要重新整理剩下的对象。

  

小结:

  Sun的文献把垃圾回收看做是低优先级的后台进程,指的是因为stop-and-copy:毕竟要暂停程序。但是在早期版本中,JVM使用的是mark-and-sweep。现在这两种回收方式通过JVM进行监视,如果有所对象都很稳定,垃圾回收器效率降低的话,就切换到mark-and-sweep。同样的,如果mark-and-sweep的效果不好,堆中出现了很多垃圾碎片(无引用对象),就会切换到stop-and-copy。在stop-and-copy使用的时候,因为内存分配以较大的“块”为单位,如果对象较大,它会占用整个块。在stop-and-copy运行的到停止程序运行的操作前,会把所有存活的对象复制到新的块(堆)中,这个时候旧的块就会被废弃,垃圾回收器就可以向废弃的块复制新对象进去,灵活利用资源。每个块都有相应的代数(count)来记录它是否还存活。如果块在某处被引用,count会增加。垃圾回收器将对上次回收动作之后新分配的块进行整理。这个対短命的对象很有帮助。垃圾回收期会定期进行完整的清理动作----大型对象仍然不会被复制,内含小型对象的那些块还是会被复制并且整理。

  JVM有许多的附加技术用以提升速度。比如JIT(Just-In-Time)编译器的技术。这个会把程序全部或部分翻译成本地机器码,程序运行速度因此快上很多。当需要装载某个类的时候(创建该类的第一个对象,后续创建不会再次装载),编译器先找到对应的class文件,然后把字节码内容装入内存中。这个时候,有两种方式可以选。其一是让JIT编译所有代码转换成机器码。但是因为装载的发生不可控制,是零散在整个程序的生命周期内的,累加起来就需要花费很多时间,并且会增加可执行代码的长度(字节码要比JIT编译后展开的机器码小很多),这个可能导致内存页面调度,从而降低程序的速度。其二是惰性评估(lazy evaluatio),意思是JIT只有在必要的时候才编译代码,这样从不会被执行的代码(import 进来,但是没有使用)就压根不会被JIT编译。JDK中的Java HotSpot技术就是采用了类似方法,代码每次执行都会做一些优化,所以执行次数越多,它的速度就越快。

java垃圾回收机制整理的更多相关文章

  1. 【转载】Java垃圾回收机制

    原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...

  2. 【转】深入理解 Java 垃圾回收机制

    深入理解 Java 垃圾回收机制   一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...

  3. 深入理解java垃圾回收机制

    深入理解java垃圾回收机制---- 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...

  4. Java垃圾回收机制_(转载)

    Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给 ...

  5. 【Java】Java垃圾回收机制

    Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给 ...

  6. Java垃圾回收机制的工作原理

    Java垃圾回收机制的工作原理 [博主]高瑞林 [博客地址]http://www.cnblogs.com/grl214 获取更多内容,请关注小编个人微信公众平台: 一.Java中引入垃圾回收机制的作用 ...

  7. 深入理解 Java 垃圾回收机制

            深入理解 Java 垃圾回收机制 一:垃圾回收机制的意义 java  语言中一个显著的特点就是引入了java回收机制,是c++程序员最头疼的内存管理的问题迎刃而解,它使得java程序员 ...

  8. Java垃圾回收机制(转)

    原文链接:Java垃圾回收机制 1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内 ...

  9. Java垃圾回收机制(Garbage Collection)

    引用博客地址:http://www.cnblogs.com/ywl925/p/3925637.html 以下两篇博客综合描述Java垃圾回收机制 第一篇:说的比较多,但是不详细 http://www. ...

随机推荐

  1. Android——Intent详解

    Intent组件虽然不是四大组件,但却是连接四大组件的桥梁,学习好这个知识,也非常的重要. 一.什么是Intent 1.Intent的概念: Android中提供了Intent机制来协助应用间的交互与 ...

  2. DDD实战4 实现产品仓储

    a.要实现仓储,首先要定义仓储接口.在领域层定义仓储接口,IProductRepository.cs. public interface IProductRepository { void Creat ...

  3. 向西项目管理工具Git一片

    前言 Git 这个词相信大家并不陌生,做开发的童鞋们每天都离不开它.当然,假设你的项目中没实用到分布式,那么,你可能从未用过 Git,当然也可能没听过.只是,这不是重点,重点是这一篇文章,我们将一起谈 ...

  4. WPF实用指南一:在WPF窗体的边框中添加搜索框和按钮

    原文:WPF实用指南一:在WPF窗体的边框中添加搜索框和按钮 在边框中加入一些元素,在应用程序的界面设计中,已经开始流行起来.特别是在浏览器(Crome,IE,Firefox,Opera)中都有应用. ...

  5. WPF中使用amCharts绘制股票K线图

    原文:WPF中使用amCharts绘制股票K线图 本想自己用GDI绘图, 通过数据直接绘制一张蜡柱图, 但觉得这样子的功能比较少, 所以到网上搜索一些能画出K线图的控件. 发现DynamicDataD ...

  6. OA 框架

    @{    Layout = null;}<!DOCTYPE html><html><head>    <meta name="viewport&q ...

  7. 在mac中如何清除.svn文件

    有些时候在开发一个应用程序我们需要用到版本控制,它可以帮助我们很好的控制我们程序的代码,尤其在多人开发的时候,优点尤为突出. 但是在有些情况下我们又认为这些.svn真的很麻烦,那么我们怎么把他们一下子 ...

  8. C#正则表达式的完全匹配、部分匹配及忽略大小写的问题

    原文:C#正则表达式的完全匹配.部分匹配及忽略大小写的问题 问题的提出 根据用户给定表达式,里面含有各种数学函数,如求绝对值,三角函数,平方.开方等,分别以类似ABS(表达式),Sin(表达式),AS ...

  9. C# 桌面软件开发-深入学习[2]- AY-C#人爱学不学-aaronyang技术分享

    原文:C# 桌面软件开发-深入学习[2]- AY-C#人爱学不学-aaronyang技术分享 1 : C# Assembly.GetEntryAssembly().GetName().Version. ...

  10. mysql 在不删除数据的时,同时重新更新主键id

    1,删除原有主键: ALTER TABLE `table_name` DROP `id`; 2,添加新主键字段:ALTER TABLE `table_name` ADD `id` MEDIUMINT( ...