浅析Java中的GC垃圾回收器的意义及与GC的交互
对象是使用new创建的,但是并没有与之相对应的delete操作来回收对象占用的内存。当我们完成对某个对象的使用时,只需停止对该对象的引用:将我们的引用改变为指向其他对象或指向null;或者从方法中返回,使得该方法的局部变量不复存在,从而使得对这些局部变量的引用变为不指向任何对象。不再被引用的对象被称为垃圾(garbage),查找并回收这些对象的过程叫做垃圾回收(garbagecollection)o
Java虚拟机利用垃圾回收来保证被引用的对象将会在内存中保留,同时会释放在执行代码中通过任何引用都不可达的对象所占用的存储空间。这是一种强保证—如果顺着从根引用(即在执行代码中可以直接访问的引用)开始的引用链可以到达某个对象,那么该对象就不会被回收。
简言之,当我们从任何可执行代码都无法到达某个对象时,它所占用的空间就可以被回收。注意,我们用的是“可以”这个词,因为内存空间是否回收是由垃圾回收器来决定的,通常情况下,只有需要更多的内存空间或者为了避免发生内存溢出时,垃圾回收器才会运行。但是程序可能在没有发生内存溢出,甚至在没有接近内存溢出的时候就退出了,所以可能根本就不需要执行垃圾回收。在当前执行的所有方法中,如果所有变量都不包含指向某个对象的引用,并且从这些变量出发,顺着引用链在所有域或数组元素中也找不到对这个对象的引用,那么我们就说这个对象是“不可达的”。
垃圾回收意味着我们永远不必担心出现虚悬引用(danglingreference)。在那些可以由程序员直接控制何时删除对象的系统中,程序员可以删除某个其他对象还在引用的对象,如果程序员删除了这样的对象,那么还在引用被删除对象的引用就会变为虚悬的,因为它们引用的是操
作系统认为是可分配的内存空间(但实际上该空间已经被释放)。系统可以将这个可分配空间分配给新的对象,这样那些原来指向该空间的引用实际上得到的对象与它们所预期的就完全不同了。在这种情况下,当程序使用存储于这个空间中的值并将其当作它们并不属于的对象来操作时,就可能会引起不可预知的灾难。垃圾回收为我们解决了虚悬引用问题,因为所有仍然被引用的对象都不会被当作垃圾回收,所以它们所占用的空间也不可能被释放。垃圾回收同时还解决了意外地多次删除同一个对象的问题—这种问题也会引发灾难。垃圾对象的回收并不需要我们的介入,但是回收垃圾会占用一定的系统资源。大量对象的创建和回收对时间关键的应用会产生干扰,因此我们在设计这种系统时,要审慎地处理创建的对象数量,以便减少要回收的垃圾数量。
垃圾回收并不能保证内存总是会有空间来创建新对象。例如,如果我们不停地创建对象,并把这些对象置于某个列表中,那么当没有足够的空间来创建新对象,同时也没有任何未被引用的对象时,就无法再创建新对象了。如果我们让上述列表保持对不再需要的对象的引用,那么就会造成内存泄漏。垃圾回收解决了很多(但并非全部)的内存分配问题。
与垃圾回收器交互
尽管Java语言本身没有任何显式地处置空闲对象的方法,我们还是可以通过直接调用垃圾回收器来寻找不再使用的对象。Runtime类以及system类中的一些便捷方法使得我们可以调用垃圾回收器,请求运行所有待运行的终结器,或者查看当前的内存状态:
.publicvoidgcQ:该方法请求Java虚拟机花费精力去回收不再使用的对象,以便能够重用这些对象所占据的内存。
.publicvoidrunFinalization():该方法请求Java虚拟机花费精力去运行如下的终结器:那些已经被发现是不可达的,但是其终结器还未执行的对象。
“publiclongfreememory():返回系统内存可用字节的估测数。
·publiclongtotalMemory():返回系统内存的总字节数。
.publiclongmaxmemoryo:返回Java虚拟机可用的系统内存的最大字节数。如果操作系统对Java虚拟机没有内存使用上的限制,将返回Long.MAX-VALUE.Java中没有任何用来设置系统最大内存的方法,通常,Java虚拟机是通过命令行或者其他配置选项来设置这个值的。
要调用上述方法,我们需要通过静态方法Runtime.getRuntime来获取对当前Runtime对象的引用。而system类支持静态的gc和runFinalization方法,它们将调用当前Runt-ime对象上的相应方法;换句话说,System.gc()与Runtime.getRuntime().gc()方法是等价的。
在调用Runtime.gc()方法时,垃圾回收器可能并不能释放出任何额外的内存,因为可能并没有垃圾可以回收,而且并非所有的垃圾回收器都可以按需发现可回收对象。因此调用垃圾回收器可能不会产生任何效果。然而,在创建大量的对象之前,特别是在垃圾回收的开销可能会对其造成影响的时间关键的应用中,调用Runtime.gc()方法还是可取的。执行它有两点潜在的好处:第一点是我们在运行应用程序之前可以得到尽可能多的内存,第二点是我们可以降低执行任务期间垃圾回收器运行的可能性。下面的方法在运行时刻积极地释放了可以释放的所有空间:
publicstaticvo记ful1GC(){ Runtimert=Runtime.getRuntime(); longisFree=rt.freeMemory(); longwasFree; do{ wasFree=isFree; rt.runFinalization(); rt.gc(); isFree二rt.freeMemory(); }while(isFree>wasFree); }
该方法在不断地循环,通过连续调用runFinalization和gc方法,freememory的值不断地增大。当空闲内存的数量不再增大时,该方法的循环也就结束了。
我们通常不需要调用runFinalization方法,因为finalize方法是由垃圾回收器异步调用的。在某些情况下,例如某项可以由finalize方法回收的资源被耗尽时,通过调用run-Finalization来强制执行尽可能多的终结才会显得有用。但是请记住,我们并不能保证任何等待被终结的对象都在使用这项资源,因此runFinalization可能不会有任何作用。
fullGc方法对于大多数应用程序来说都显得过于激进。在需要强制进行垃圾回收的特殊情况下,对system.gc方法的单次调用所收集到的垃圾即便不是全部的可利用垃圾,也是其中的绝大部分,因此重复调用会降低垃圾回收的产出率,而且在许多系统中,这些重复调用是毫无产出的。