- finalize
java允许在类中定义一个名为finalize()的方法:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下次垃圾回收动作发生时,才会真正回收对象占用的内存。
一般情况下,我们不要过多使用finalize,它的存在只是为了处理java中在使用“本地方法”情况下调用C的malloc()分配存储空间,然后需要在finalize()中调用本地方法free()去释放内存。 - 垃圾回收机制
1)基于计数技术
对个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1.当引用离开作用域或指向其他对象或被置为null时,引用计数减1.垃圾回收器会在含有全部对象的列表遍历,当发现某个对象的引用计数为0时,就释放器占用的空间。
缺陷:如果对象之间存在循环引用,可能会出现“对象应该被回收,但引用计数不为零”的情况。所需工作量极大。
2)“活”对象
对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果从堆栈和静态存储区开始,遍历(类似于图)所有的引用,就能找到所有“活”的对象。这就解决了“交互自引用的对象组”的问题——这种对象根本不会被发现,因此也就被自动回收了。
3)自适应(基于“活”对象)
在这种方式下,Java虚拟机将采用一种自适应的垃圾回收技术。至于如何处理找到的存活对象,取决于不同的Java虚拟机实现。 - 具体实现
1)停止-复制
先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列。同时,所有指向这些对象的引用都必须修正。位于堆或静态存储区的引用可以直接被修正,但还有其他指向这些对象的引用,它们要在遍历的过程中才能被找到(可以想象成有个表格,将旧地址映射至新地址)。
缺点:效率低,主要两个原因。首先得有两个堆,然后在这两个分离的堆之间来回倒腾,从而得维护比实际需要多一倍的空间。第二个问题在于复制,程序在进入稳定状态之后,可能只产生少量垃圾,甚至没有垃圾,尽管如此,复制式回收器仍然会将所有内存自一处复制到另一处,这很浪费。
2)标记-清扫
要是没有新垃圾产生,就会转换到另一种工作模式(即“自适应”)——标记-清扫。从堆栈和静态存储区出发,遍历所有引用,进而找出所有存活的对象。每当找到一个存活对象,就会给对象设一个标志,这个过程不会回收任何对象,只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的。标记-清扫方式速度相当慢,但是只产生少量垃圾甚至不产生垃圾时,它的速度就很快了。
3)分代
内存以“块”为单位。如果对象较大,它会占用单独的块,每个块都有相应的代数来记录它是否还存活。垃圾回收器会定期进行完整的清理动作——大型对象不会被复制(只是其代数会增加),内含小型对象的那些块则是被复制并整理。
总结:Java虚拟机会进行监视,如果所有对象都很稳定,切换到“标记-清扫”方式;同样,要是堆空间出现很多碎片,就会切换回“停止-复制”方式。这就是“自适用”技术。
Java虚拟机中有许多附件技术用以提升速度。例如“即时”(Just-In-Time,JIT)编译器技术。采用惰性评估,即时编译器只在必要的时候编译代码。这样从不会被执行的代码也许就压根不会被JIT编译。代码每次被执行的时候都会做一些优化,所以执行的次数越多,它的速度就越快。
网友评论