1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。
Java的垃圾收集(Garbage Collection)主要关注堆和方法区的内存回收。
在GC堆进行回收前,第一件事情就是要确定哪些对象还活着,哪些对象已经死亡,需要被回收。
判断对象是否存活的算法:
1)引用计数器(Reference Counting)【Java的GC不使用此算法】:
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值减1,计数器为0的对象就是不可能再被使用的。
使用者:微软COM技术、使用ActionScript3的FlashPlayer、Python、Squirrel、……
缺陷:很难解决对象之间的互相循环引用问题。
2)根搜索算法(GC Roots Tracing)【Java的GC使用此算法】:
通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
使用者:Java、C#、Lisp、……
在Java语言里,可以作为GC Roots的对象包括:
a)虚拟机栈中的引用对象。
b)方法区中的类静态属性引用的对象。
c)方法区中的常量引用的对象。
d)本地方法栈中JNI的引用的对象。
引用类型(Reference):
引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference) 、虚引用(Phanton Reference),引用强度依次逐渐减弱。
强引用:普遍存在的引用,GC永远不会回收强引用的对象。
软引用:还有用,但并非必须的对象,在系统将要发生内存溢出时将此类对象列进回收范围并进行第二次回收。
弱引用:非必须的对象,只能生存到下一次垃圾收集发生之前。
虚引用:无法通过虚引用获取到对象,为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。
要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行根搜索后发现没有与GC Roots相连的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。如果对象被判断为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue的队列中,并在稍后由一条由虚拟机自动建立的、低优先级的finalizer线程去执行。
垃圾收集算法:
a)标记-清除算法(Mark-Sweep)是最基础的收集算法,分“标记”和“清除”两个阶段。
缺点:效率问题,标记和清除过程的效率都不高;空间问题,标记清除后会产生大量不连续的内存碎片。
b)复制算法(Copying)将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用过的内存空间一次清理掉。
内存代价太高。商业虚拟机都采用这种收集算法,IBM研究表明,新生代中的对象98%是朝生夕死的,所以并不需要安装1:1的比例来划分内存空间。
c)标记-整理算法(Mark-Compact)让所以存活的对象都向一端移动,然后直接清理掉端边界意外的内存。
d)分代收集算法(Generational Collection)根据对象的存活周期不同将内存划分为几块,一般是把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。
垃圾收集器(基于Sun HotSpot_1.6_u22):
如果说垃圾收集算法是内存回收的方法论,而垃圾收集器就是内容回收的具体实现。
Young Generation:Serial、ParNew、Parallel Scavenge。
Tenured Generation:CMS、Serial Old(MSC)、Parallel Old。
Both:G1。
内存分配与回收策略:
对象优先在Eden区分配。
大对象直接进入老年代。
长期存活的对象将进入老年代。