GC:垃圾回收站,是将java的无用的堆对象进行清理,释放内存,以免发生内存泄露。在介绍java回收站前,首先介绍下几种回收机制
1. 引用计数:
当一个对象A被其他对象B引用时,对象A引用+1,断开引用则-1,GC工作时,会检查所有对象中的引用计数,如果为0则代表要清除,>0则表示有其他对象引用不能清除。这种机制有一个致命缺点,就是当两个对象互引用时,在遍历时可能会发生这两个对象引数永远不为0,则永远不会被删除。此机制java从不使用。
2. 停止-复制:
程序暂停运行,启动GC,GC从堆或静态存储区开始遍历所有对象,判断对象是否“活”的对象,如果是活不删除,反之删除。判断是否“活”就是判断该对象是否有被其他对象引用,从链上去查找。当是活对象时,GC会从另外堆里开避一个大空间,然后将活对象复制一份到新空间里,在复制时按着紧密排列,同时更新所有引用地址为新的地址,不活对象原封不动。遍历完后,再遍历一次,这次是将不活的对象彻底给删除。
优点:所有对象能够重新紧密排列,不会出现内存碎片,这对以后创建对象能提供更快的效率。
缺点:占用的内存空间大,要原对象的2倍空间;这种模式无论如何所有活的对象都要复制一份,假设遍历到最后,对象很稳定,只出现少量垃圾对象或者根本没垃圾对象,这时已经做了复制工作,浪费了资源。
3. 标记-删除:
程序暂停运行,启动GC,GC从堆或静态存储区开始遍历所有对象,判断对象是否“活”的对象,如果是活不删除,反之删除。判断是否“活”就是判断该对象是否有被其他对象引用,从链上去查找。当是活对象时,会给对象给个标记符号,死对象则不标记,遍历完后,第二次遍历时,只保留标记的对象,把所有没标记的对象都删除。
以上第2和第3种都是要遍历两次,而且都是最后一次才执行真正删除,比起第1种来说要慢很多,因为要遍历2次,尤其是第2种,还要复制动作,但这2种归避了第1种的致命缺点。
早期的jvm采用的是标记-删除模式,继java5后,jvm采用自适应技术,就是根据自身状态情况,在”停止-复制“和”标记-删除“自动切换以达到最快效率。在第1遍历时,如果确定对象需要回收,则会先执行对象的finalize()方法。
java GC具体工作原理:
JVM分配内存是以较大的块为单位,如果对象为较大,则该对象本身为一个块。GC先启动“停止-复制”模式,遍历对象时,遇到活对象,则把对象复制到新块里,同时块代数+1,如果对象较大,则保持不动,代数+1;如果检测到垃圾对象稀少,则自动切换到“标记-删除”模式,如果检测到对象分布到内存太零散,则又会自动切换到“停止-复制”模式来重新整理内存紧密分布。这就是自适应技术。
JVM分配内存时,有一个堆指针,堆指针指向当前最后一个被分配的内存块,由于“停止-复制”模式,对象大部分都是连续排列的,则堆指针移动下一格,就是尚未分配的内存,这时就不用在整个堆空间里查找未分配的内存了,这对创建速度有很大提高。
关于对象的finalize()方法:
finalize方法是指对象完成时执行的一些清理工作,是Object里的受保护方法,在外界不能调用。实质上这个方法是给GC调用的,什么时候调用以上有讲。此方法是强调进一步对象需要释放非托管对象,是一个检测保险的作用,比如一个类里包含访问一个流内容,如下:
class AccessStream
{
private InputStream stream;
private boolean isClose = false;
/* (non-Javadoc)
* @see java.lang.Object#finalize()
*/
@Override
protected void finalize() throws Throwable
{
if (!isClose)
{
stream.close();
}
super.finalize();
}
}
此类在操作流完后,正确作法是,应该即时关闭流(调用close());有时因为程序员某些租心大意原因,并没及时关闭流,这时重写finalize() 作最后保险作用,当没有关闭时,释放此对象前关闭流。
通常不建议重写finalize,除了要求程序员严格习惯,最重要的是finalize并不能马上执行,即使是显式调用System.gc() 也不保证立刻执行,只能说建议GC执行,GC是否要执行,要看当前内存占用量等因素。如果finalize不能马上执行,这就意味着本来应该早点释放流,而出现很长时间才释放。
GC算法?
在C/C++中是由程序员自己去申请、管理和释放内存的,因此没有GC的概念。而在Java中,专门有一个用于垃圾回收的后台线程来进行监控、扫描,自动将一些无用的内存进行释放。下面介绍几种常见的GC算法。
引用计数法 Reference Counting
给对象添加一个引用计数器,每过一个引用计数器值就+1,少一个引用就-1。当它的引用变为0时,该对象就不能再被使用。它的实现简单,但是不能解决互相循环引用的问题。
根搜索算法 GC Roots Tracing
以一系列叫“GC Roots”的对象为起点开始向下搜索,走过的路径称为引用链(Reference Chain),当一个对象没有和任何引用链相连时,证明此对象是不可用的,用图论的说法是不可达的。那么它就会被判定为是可回收的对象。
JAVA里可作为GC Roots的对象
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(即Native方法)的引用的对象
标记-清除算法 Mark-Sweep
这是一个非常基本的GC算法,它是现代GC算法的思想基础,分为标记和清除两个阶段:先把所有活动的对象标记出来,然后把没有被标记的对象统一清除掉。但是它有两个问题,一是效率问题,两个过程的效率都不高。二是空间问题,清除之后会产生大量不连续的内存。
复制算法 Copying
复制算法是将原有的内存空间分成两块,每次只使用其中的一块。在GC时,将正在使用的内存块中的存活对象复制到未使用的那一块中,然后清除正在使用的内存块中的所有对象,并交换两块内存的角色,完成一次垃圾回收。它比标记-清除算法要高效,但不适用于存活对象较多的内存,因为复制的时候会有较多的时间消耗。它的致命缺点是会有一半的内存浪费。
标记整理算法 Mark-Compact
标记整理算法适用于存活对象较多的场合,它的标记阶段和标记-清除算法中的一样。整理阶段是将所有存活的对象压缩到内存的一端,之后清理边界外所有的空间。它的效率也不高。