赞
踩
在C语言中,有些由内存需要程序员在代码中进行手动回收,但是在Java中,没有这样的声明式操作。有没有人有去想过,Java到底做了什么可以自动进行垃圾回收呢?Java中的垃圾回收,是一点都不需要程序员关心,万无一失的吗?
这章里我们就一起来聊聊,Jvm中的垃圾收集器和内存分配策略。
垃圾收集(Garbage Collection,GC),并不是随着Java一起诞生的。GC的历史比Java来得更加久远,早在1960年的时候,MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考GC需要完成的三件事情:□ 哪些内存需要回收?□ 什么时候回收?□ 如何回收?
在经过半个世纪的发展后,对于这三个问题的答案越来越清晰,总结成就是:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
在Java程序编写的过程中,我们可以知道代码的逻辑是怎样的,但是具体的分支只有在运行过程中才能知道。而这部分的内存分配和回收也是动态进行的,垃圾收集器主要关注的就是这部分内存。
那么实际中,一个需要解决的问题就是,如何判断对象是否存活,对于不再存活的对象,进行垃圾回收。
在经过漫长的发展后,目前主要有下面几种算法来进行对象存活判断。
算法的定义为:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。
这是实现简单,且效率非常高效的一种算法。在redis、python的虚拟机、FlashPlayer等应用中,也都有采用这样的算法。但是Java中并没有采用这样的算法实现,主要原因是其存在相互循环引用的问题。
简单来说,A对象引用B对象,B对象引用A对象的情况下。A和B互相引用,于是他们的计数器都不会为0,于是GC收集器便就永远无法回收他们。
算法的定义为:通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连,或者说不可达的时候,则证明此对象不可用。
在Java语言中,可以作为GC Roots的对象包括下面几种:
在早期的JDK定义中,引用的定义为,如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。但这样的定义方式过于纯粹,一个对象只有两种状态,即被引用或者没有被引用两种。对于一些缓存类型的数据,则显得有些鸡肋,更无法体现内存分配的价值。
之后JDK对于引用进行了概念扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。
在根搜索算法中,在GCRoots没有可以到达的引用链之后,就一定会“死亡”吗?其实也不一定,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行根搜索后发现没有与GCRoots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
当这个对象需要执行finalize()方法时,这个对象会被放置在一个名为F-Queue的队列中,并稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里的“执行”是虚拟机会触发这个方法,但并不一定会等待它运行结束。因为如果对象在finalize()方法中死循环或者超长时间执行,可能导致F-Queue队列中的其他对象永久处于等待状态,甚至可能导致内存回收系统奔溃。
finalize()方法是对象可以存活的最后一次机会,在这里可以将自己和引用链上的任何一个对象建立关联即可,否则就会进入到垃圾回收的系统中。但finalize()依旧是一种充满不确定性的方法,在诞生之初亦是为了C/C++程序员的更容易接受的一种妥协,推荐目前的try-finally方法处理更加优雅,也更安全可靠。
Java虚拟机浅谈系列 —— 往期文章:郑小民:Java虚拟机浅谈--探索Java堆zhuanlan.zhihu.com
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。