什么是GC?

热点 162 0

问:什么是GC?

什么是GC?

答: 在Java语言中,垃圾回收(Garbage Collection,GC)是一个非常重要的概念,它的主要作用是回收程序中不再使用的内存

在使用C/C++语言进行程序开发时,开发人员必须非常仔细地管理好内存的分配与释放,如果忘记或者错误地释放内存往往会导致程序运行不正常甚至是程序崩溃。为了减轻开发人员的工作,同时增加系统的安全性与稳定性,Java语言提供了垃圾回收器来自动检测对象的作用域,可自动地把不再被使用的存储空间释放掉。

具体而言,垃圾回收器要负责完成3项任务:分配内存、确保被引用对象的内存不被错误地回收以及回收不再被引用的对象的内存空间。

垃圾回收器的存在一方面把开发人员从释放内存的复杂工作中解脱出来,提高了开发人员的生产效率;另一方面,对开发人员屏蔽了释放内存的方法,可以避免因开发人员错误地操作内存而导致应用程序的崩溃,保证了程序的稳定性。

但是,垃圾回收也带来了问题,为了实现垃圾回收,垃圾回收器必须跟踪内存的使用情况,释放没用的对象,在完成内存的释放后还需要处理堆中的碎片,这些操作必定会增加JVM的负担,从而降低程序的执行效率。

对对象而言,如果没有任何变量去引用它,那么该对象将不可能被程序访问,因此可以认为它是垃圾信息,可以被回收。只要有一个以上的变量引用该对象,该对象就不会被垃圾回收。

对于垃圾回收器来说,它使用有向图来记录和管理堆内存中的所有对象,通过这个有向图就可以识别哪些对象是“可达的”(有引用变量引用它就是“可达的”),哪些对象是“不可达的”(没有引用变量引用它就是不可达的),所有“不可达”对象都是可被垃圾回收的,示例如下:

上述代码在执行到i2=i1后,内存的引用关系如下图所示:

此时,如果垃圾回收器正在进行垃圾回收操作,在遍历上述有向图时,资源2所占的内存是不可达的,垃圾回收器就会认为这块内存已经不会再被使用了,因此就会回收该块内存空间。

垃圾回收都是依据一定的算法进行的,下面介绍其中几种常用的垃圾回收算法。

引用计数算法(Reference Counting Collector)

引用计数作为一种简单但是效率较低的方法,其主要原理如下:在堆中对每个对象都有一个引用计数器;当对象被引用时,引用计数器加1;当引用被置为空或离开作用域的时,引用计数减1,由于这种方法无法解决相互引用的问题,因此JVM没有采用这个算法。

追踪回收算法(TracingCollector)

追踪回收算法利用JVM维护的对象引用图,从根结点开始遍历对象的应用图,同时标记遍历到的对象。当遍历结束后,未被标记的对象就是目前已不被使用的对象,可以被回收了。

压缩回收算法(CompactingCollector)

压缩回收算法的主要思路如下:把堆中活动的对象移动到堆中一端,这样就会在堆中另外一端留出很大的一块空闲区域,相当于对堆中的碎片进行了处理。虽然这种方法可以大大简化消除堆碎片的工作,但是每次处理都会带来性能的损失。

复制回收算法(CopingCollector)

复制回收算法的主要思路如下:把堆分成两个大小相同的区域,在任何时刻,只有其中的一个区域被使用,直到这个区域的被消耗完为止,此时垃圾回收器会中断程序的执行,通过遍历的方式把所有活动的对象复制到另外一个区域中,在复制的过程中它们是紧挨着布置的,从而可以消除内存碎片。当复制过程结束后程序会接着运行,直到这块区域被使用完,然后再采用上面的方法继续进行垃圾回收。

这个算法的优点是在进行垃圾回收的同时对对象的布置也进行了安排,从而消除了内存碎片。但是这也付出了很高的代价:对于指定大小的堆来说,需要两倍大小的内存空间;同时由于在内存调整的过程中要中断当前执行的程序,从而降低了程序的执行效率。

按代回收算法(GenerationalCollector)

复制回收算法主要的缺点如下:每次算法执行时,所有处于活动状态的对象都要被复制,这样效率很低。由于程序有“程序创建的大部分对象的生命周期都很短,只有一部分对象有较长的生命周期”的特点,因此可以根据这个特点对算法进行优化。按代回收算法的主要思路如下:把堆分成两个或者多个子堆,每一个子堆被视为一代。算法在运行的过程中优先收集那些“年幼”的对象,如果一个对象经过多次收集仍然“存活”,那么就可以把这个对象转移到高一级的堆里,减少对其的扫描次数。

常见笔试题:

现有如下代码:

当Float对象在第2行被创建后,什么时候能够被垃圾回收?()

A.4行以后

B.5行以后

C.6行以后

D.7行以后

答案:C。在第6行后不再有对象引用Float对象了,因此能够被垃圾回收。

2.下列关于垃圾回收的说法中,正确的是()。

A.一旦一个对象成为垃圾,就立刻被回收掉

B.对象空间被回收掉之后,会执行该对象的finalize方法

C.finalize方法和C++的析构函数完全是一回事情

D.一个对象成为垃圾是因为不再有引用指着它,但是线程并非如此

答案:D。成为垃圾的对象,只有在下次垃圾回收器运行时才会被回收,而不是马上被清理,因此选项A错误。finalize方法是在对象空间被回收前调用的,因此选项B错误。在C++语言中,调用了析构函数后,对象一定会被销毁,而Java语言调用了finalize方法,垃圾却不一定会被回收,因此finalize方法与C++的析构函数是不同的,所以选项C也不正确。对于D,当一个对象不再被引用后就成为垃圾可以被回收,但是线程就算没有被引用也可以独立运行的,因此与对象不同。所以正确答案为D。

3、是否可以主动通知JVM进行垃圾回收?

答案:由于垃圾回收器的存在,Java语言本身没有给开发人员提供显式释放已分配内存的方法,也就是说,开发人员不能实时地调用垃圾回收器对某个对象或所有对象进行垃圾回收。但开发人员却可以通过调用System.gc()方法来“通知”垃圾回收器运行,当然,JVM也并不会保证垃圾回收器马上就会运行。由于System.gc()方法的执行会停止所有响应,去检查内存中是否有可回收的对象,这会对程序的正常运行以及性能造成极大的威胁,因此实际编程时,不推荐频繁使用这一方法。


标签: gc

抱歉,评论功能暂时关闭!