程序计数器、虚拟机栈、本地方法栈随线程而生,也随线程而灭;栈帧随着方法的开始而入栈,随着方法的结束而出栈。这几个区域的内存分配和回收都具备肯定性,在这几个区域内不须要过多考虑回收的问题,由于方法结束或者线程结束时,内存天然就跟随着回收了。java
而对于 Java 堆和方法区,咱们只有在程序运行期间才能知道会建立哪些对象,这部份内存的分配和回收都是动态的,垃圾收集器所关注的正是这部份内存。算法
一个对象不被任何对象或变量引用,就是无效对象,须要被回收。缓存
在对象头维护着一个 counter 计数器,对象被引用一次则计数器 +1;若引用失效则计数器 -1。当计数器为 0 时,就认为该对象无效了。this
引用计数算法的实现简单,断定效率也很高,在大部分状况下它都是一个不错的算法。可是主流的 Java 虚拟机里没有选用引用计数算法来管理内存,主要是由于它很难解决对象之间循环引用的问题。线程
对象 objA 和 objB 都有字段 instance,令 objA.instance = objB 而且 objB.instance = objA,因为它们互相引用着对方,致使它们的引用计数都不为0,会使得垃圾收集器永远没法回收这两个对象。
全部和 GC Roots 直接或间接关联的对象都是有效对象,和 GC Roots 没有关联的对象就是无效对象。对象
GC Roots 是指:生命周期
GC Roots 并不包括堆中对象所引用的对象,这样就不会有循环引用的问题。队列
断定对象是否存活与“引用”有关。在 JDK 1.2 之前,Java 中的引用定义很传统,一个对象只有被引用或者没有被引用两种状态,咱们但愿能描述这一类对象:当内存空间还足够时,则保留在内存中;若是内存空间在进行垃圾手收集后仍是很是紧张,则能够抛弃这些对象。不少系统的缓存功能都符合这样的应用场景。内存
在 JDK 1.2 以后,Java 对引用的概念进行了扩充,将引用分为了如下四种:虚拟机
相似 "Object obj = new Object()" 这类的引用,就是强引用,只要强引用存在,垃圾收集器永远不会回收被引用的对象。
软引用是用来描述一些有用但并不是必需的对象,软引用关联着的对象,在系统即将发生内存溢出时,会将这些对象列入回收范围进行回收。若是回收事后尚未足够的内存,才抛出内存溢出异常。
弱引用也是用来描述非必需对象的,可是它的强度比软引用更弱一些。当 JVM 进行垃圾回收时,不管内存是否充足,都会回收被软引用关联的对象。
虚引用也称幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,彻底不会对其生存时间构成影响。为一个对象设置虚引用关联的惟一目的就是能在这个对象被收集器回收时收到一个系统通知。
对于可达性分析中不可达的对象,也并非没有存活的可能。
JVM 会判断此对象是否有必要执行 finalize() 方法,若是对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,那么视为“没有必要执行”。那么对象基本上就真的被回收了。
若是对象被断定为有必要执行 finalize() 方法,那么对象会被放入一个 F-Queue 队列中,虚拟机会以较低的优先级执行这些 finalize()方法,但不会确保全部的 finalize() 方法都会执行结束。若是 finalize() 方法出现耗时操做,虚拟机就直接中止指向该方法,将对象清除。
若是在执行 finalize() 方法时,将 this 赋给了某一个引用,那么该对象就重生了。若是没有,那么就会被垃圾收集器清除。
任何一个对象的 finalize() 方法只会被系统自动调用一次,若是对象面临下一次回收,它的 finalize() 方法不会被再次执行,想继续在 finalize() 中自救就失效了。
方法区中存放生命周期较长的类信息、常量、静态变量,每次垃圾收集只有少许的垃圾被清除。方法区中主要清除两种垃圾:
只要常量池中的常量不被任何变量或对象引用,那么这些常量就会被清除掉。
断定一个类是不是“无用的类”,条件较为苛刻。
一个类被虚拟机加载进方法区,那么在堆中就会有一个表明该类的对象:java.lang.Class。这个对象在类被加载进方法区时建立,在方法区该类被删除时清除。
学会了如何断定无效对象、无用类、废弃常量以后,也就知道了垃圾收集器会清除哪些数据,接下来介绍如何清除这些数据。
判断哪些数据须要清除,并对它们进行标记,而后清除被标记的数据。
这种方法有两个不足:
为了解决效率问题,“复制”收集算法出现了。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完,须要进行垃圾收集时,就将存活者的对象复制到另外一块上面,而后将第一块内存所有清除。这种算法有优有劣:
为了解决空间利用率问题,能够将内存分为三块: Eden、From Survivor、To Survivor,比例是 8:1:1,每次使用 Eden 和其中一块 Survivor。回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 空间上,最后清理掉 Eden 和刚才使用的 Survivor 空间。这样只有 10% 的内存被浪费。
可是咱们没法保证每次回收都只有很少于 10% 的对象存活,当 Survivor 空间不够,须要依赖其余内存(指老年代)进行分配担保。
为对象分配内存空间时,若是 Eden+Survivor 中空闲区域没法装下该对象,会触发 MinorGC 进行垃圾收集。但若是 Minor GC 事后依然有超过 10% 的对象存活,这样存活的对象直接经过分配担保机制进入老年代,而后再将新对象存入 Eden 区。
在回收垃圾前,首先将废弃对象作上标记,而后将未标记的对象移到一边,最后清空另外一边区域便可。
这是一种老年代的垃圾收集算法。老年代的对象通常寿命比较长,所以每次垃圾回收会有大量对象存活,若是采用复制算法,每次须要复制大量存活的对象,效率很低。
根据对象存活周期的不一样,将内存划分为几块。通常是把 Java 堆分为新生代和老年代,而后根据各个年代的特色采用最适当的收集算法。