最近在读《java编程思想》,读到垃圾回收,作一下笔记,也供大家参考。
java的垃圾回收器只知道释放由new()分配的内存,不知道如何释放那些没有使用new创建的对象。java在基类中定义了一个finalize()方法,它的工作原理“假定”为:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的空间。
但是,java中的对象并非总被回收。只要程序没有濒临存储空间用完的时候,对象占用的空间就总也得不到释放。因为垃圾回收本身也有开销,要是不使用它,就不用支付这部分开销。
首先,不应该将finalize()作为通用的清理方法。
垃圾回收只与内存有关。无论对象时如何创建的,垃圾回收器都会负责释放对象占据的所有内存。finalize()的使用被限制到一种特殊情况:
本地方法:java中调用非java代码的方式,本地方法目前只支持C和C++.
调用本地方法分配的内存空间,需要在finalize()中用本地方法调用清理内存函数(比如free())。。所以不要过多使用finalize()。
java虚拟机(JVM)如果并未面临内存耗尽的情况,是不会浪费时间去执行垃圾回收的。
System.gc()用于强制终结动作。
垃圾回收工作时,将一面回收空间,一面使堆中的对象紧凑排列,这样“堆指针”就可以很容易移动到更靠近传送带的开始处,也就尽量避免了页面错误。通过垃圾回收器对对象的重新排列,实现了一种高速的、有无限空间可供分配的堆模型。
引用计数法是一种简单但是速度很慢的垃圾回收技术。每个对象都有一个计算器,当有引用连接至对象时,引用计数+1,当引用离开作用域或者被置为null时,引用计数-1.虽然管理引用计数的开销不大,但是这项开销在真个程序生命周期中持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间。但是这种方法有个缺陷,就是对象之间的存在循环引用,会出现问题,对象该被回收,但是计数不等于0.【该方法从未被应用于任何一种java虚拟机实现】
该模式依据的思想:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。由此,如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。
在这种方式下,java虚拟机将采用一种自适应的垃圾回收技术。至于如何处理找到的存活对象,取决于不同的java虚拟机实现。有两种方法实现该模式:
【停止-复制(stop-copy)】
该方法需要先暂停程序的运行(所以不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列。然后可以简单、直接分配内存空间了。
但是该模式效率低。原因有两个。首先,得有两个堆,然后在这两个分离的堆之间来回倒腾,从而维护比实际多一倍的空间。某些java虚拟机堆此问题处理方式是:按需从堆中分配几块大的内存,复制动作发生在这些大块内存中。第二个问题,程序在稳定后,可能只会产生少量的垃圾,甚至没有垃圾,但是依然会执行该模式,造成浪费。
所以一些java虚拟机会进行检查:要是没有新垃圾产生,就会转换到另一种工作模式:标记-清除(mark-and-sweep)。
【标记-清除(mark-and-sweep)】
从堆栈和静态存储区出发,遍历所有的引用,进而找出所有的对象。每当找到一个存活对象,就会给对象设一个标记,这个过程不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。清理过程中,没有标记的对象会释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间,就得重新整理剩下的对象 。该模式工作也必须在程序暂停的情况下才能进行。
最终,java虚拟机将采用一种自适应的垃圾回收技术。java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记-清除”方式。同样,java虚拟机会跟踪‘“标记-清除”的效果,要是堆空间出现很多碎片,就会切换到“停止-复制”方式。
参考《java编程思想 Thinking in Java》