JVM中最重要的功能之一就是自动的垃圾回收机制了
JVM就像一个大工厂,要想掌控工厂,就必须了解JVM的种种设计
对象已死?
判断一个生命是否死亡似乎是简单的,但是内存中的对象可不是这么简单
怎样的对象才算死亡?这个问题大牛们似乎给出了合理的答案
当一个对象不存在指向它的引用,这个对象就可能判定为死亡。
JVM为了能够准确的判断对象的是否死亡,设计了种种算法,最主要的两种算法是
引用计数算法与可达性分析算法
引用计数算法
引用计数法的原理很简单,给对象添加一个引用计数器,每次引用就加一,失效就减一
问题:对象之间相互循环引用,则永远不可能为0
可达性分析算法
主流商用程序语言的主流实现中,都是通过可达性分析来判断对象是否存活。
思路:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(不可达),则证明此对象不可用。
GC Roots的对象包括:
-
虚拟机栈(栈帧中的本地变量表)中引用的对象
栈帧中的本地变量表:
栈帧
对应一个Java方法,局部变量表存放方法参数
和方法内部定义的局部变量
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中JHI(即一般说的Native方法) 引用的对象
总结为:线程栈帧中正在使用的对象,各种静态对象
另外一种对象
除去上面的生死,还有一种对象
“食之无味,弃之可惜”:当内存足够,我们希望能保留它。当内存不足时,我们可以抛弃这些对象。
Java对引用进行了细致的划分
-
强引用
在程序代码中普遍存在
正常使用的new就是一种强引用
强引用只要存在就不会回收被引用的对象
-
软引用
描述一些还有用并非必需的对象
在系统将要发生内存溢出前,将会把这些对象列进垃圾回收的范畴。
SoftReference类来实现
-
弱引用
描述非必需对象,强度弱于软引用
弱引用关联的对象只能生存到下一次垃圾收集发生前
WeakReference来实现
-
虚引用
幽灵引用或幻影引用。
能在这个对象被垃圾收集器回收时收到一个系统通知
PhantomReference实现
垃圾回收步骤
一次筛选
对象被可达性分析标记后,将筛选出来需要执行finalize()方法的对象,将这些对象放在名为F-Queue的队列之中。
稍后将有虚拟机自动建立的、低优先级的Finalizer线程传递执行命令给该对象。
执行命令表示,虚拟机只负责摁下发射按钮,至于命中目标,虚拟机不会负责。
这样可以防止队列卡死或执行缓慢
二次筛选
虚拟机垃圾回收器小等一下队列,便会去检查这个队列,若此时的对象找到了属于他的引用(可以把this赋值给某个变量),这个对象将“成功逃生”,GC将会把该对象移除即将回收的集合。
回收方法区
方法区存放的是各种类信息,垃圾收集的效率远低于新生代。
主要为两部分内容:废弃常量和无用的类
废弃常量
如“abc”在没有使用的情况下,在必要的情况下,会被系统清理出常量池。
无用的类
- 该类所有的实例都已经被回收。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有任何地方引用,无法在任何地方通过反射访问该类的方法。
满足上述3个条件,可以被回收,但并不一定被回收。
垃圾收集算法
标记-清除
步骤
- 标记所有需要回收的对象
- 标记完成后统一回收
不足
- 效率低。标记和清除两个过程效率都不高。
- 空间问题。标记清除后会产生大量不连续内存碎片。
复制
过程
将某一块内存区域活着的对象整体复制到新的内存块,然后将已使用过的内存空间一次性清理掉
不足
- 方法简单高效,但内存区域缩小为原来的一半,太消耗资源。
- 在对象存活率较高时就要进行较多的复制操作,效率将变低。
IBM公司研究表明,新生代大多数对象“朝生夕死”,不需要按照1:1的比例划分,将内存分为一块较大的
Eden
空间和两块较小的Survivor
空间。回收时,Eden和Survivor中还活着的对象一次性地复制到另外一块Survivor空间上。最后清理Eden和刚才使用过的Survivor空间。
HotSpot虚拟机默认大小比例为8:1
Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。
标记-整理
- 标记存活对象。
- 所有存活对象向一端移动。
- 清理掉端边界以外的内存。
分代收集
目前商业虚拟机都采用"分代收集"
根据对象的存活周期的不同将内存划分为几块。一般分为新生代和老年代。
根据各个年代的特点采用适当的收集算法。