相关文章
Java虚拟机系列javascript
这一节咱们来简单的介绍垃圾收集器,并学习垃圾标记的算法:引用计数算法和根搜索算法,为了更好的理解根搜索算法,会在文章的最后介绍Java对象在虚拟机中的生命周期。html
垃圾收集器(Garbage Collection),一般被称做GC。提到GC,不少人认为它是伴随Java而出现的,其实GC出现的时间要比Java早太多了,它是1960诞生于MIT的Lisp。
GC主要作了两个工做,一个是内存的划分和分配,一个是对垃圾进行回收。关于内存的划分和分配,目前Java虚拟机内存的划分是依赖于GC的的设计的,好比如今GC都是采用了分代收集算法来回收垃圾,Java堆做为GC主要管理的区域,被细分为新生代和老年代,再细致一点新生代又能够划分为Eden空间、From Survivor空间、To Survivor空间等,这样进行划分是为了更快的进行内存分配和回收。空间划分后,GC就能够为新对象分配内存空间。
关于对垃圾进行回收,被引用的对象是存活的对象,而不被引用的对象是死亡的对象也就是垃圾,GC要区分出存活的对象和死亡的对象,也就是垃圾标记,并对垃圾进行回收。接下来咱们先来介绍垃圾标记算法。java
在对垃圾进行回收前,GC要先标记出垃圾,那么如何标记呢,目前有两种垃圾标记算法,分别是引用计数算法和根搜索算法,这两个算法都和引用有些关联,所以讲垃圾标记算法前,咱们先回顾下引用的知识。算法
在JDK1.2以后,Java将引用分为强引用、软引用、弱引用和虚引用。性能优化
引用计数算法的基本思想就是每一个对象都有一个引用计数器,当对象在某处被引用的时候,它的引用计数器就加1,引用失效时就减1。当引用计数器中的值变为0,则该对象就不能被使用成了垃圾。
目前主流的Java虚拟机没有选择引用计数算法来为垃圾标记,主要缘由是引用计数算法没有解决对象之间相互循环引用的问题。
举个例子,下面代码的注释1和注释2处,d1和d2相互引用,除此以外这两个对象无任何其余引用,实际上这两个对象已经死亡,应该做为垃圾被回收,可是因为这两个对象互相引用,引用计数就不会为0,垃圾收集器就没法回收它们。微信
class _2MB_Data {
public Object instance = null;
private byte[] data = new byte[2 * 1024 * 1024];//用来占内存,测试垃圾回收
}
public class ReferenceGC {
public static void main(String[] args) {
_2MB_Data d1 = new _2MB_Data();
_2MB_Data d2 = new _2MB_Data();
d1.instance = d2;//1
d2.instance = d1;//2
d1 = null;
d2 = null;
System.gc();
}
}复制代码
若是你使用Android Studio,就在Edit Configurations中的VM options加入以下语句来输出详细的GC日志:jsp
-XX:+PrintGCDetails复制代码
运行程序,GC日志为:
[GC (System.gc()) [PSYoungGen: 8028K->832K(76288K)] 8028K->840K(251392K), 0.0078334 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 832K->0K(76288K)] [ParOldGen: 8K->603K(175104K)] 840K->603K(251392K), [Metaspace: 3015K->3015K(1056768K)], 0.0045844 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 76288K, used 1966K [0x000000076af80000, 0x0000000770480000, 0x00000007c0000000)
eden space 65536K, 3% used [0x000000076af80000,0x000000076b16bac0,0x000000076ef80000)
from space 10752K, 0% used [0x000000076ef80000,0x000000076ef80000,0x000000076fa00000)
to space 10752K, 0% used [0x000000076fa00000,0x000000076fa00000,0x0000000770480000)
ParOldGen total 175104K, used 603K [0x00000006c0e00000, 0x00000006cb900000, 0x000000076af80000)
object space 175104K, 0% used [0x00000006c0e00000,0x00000006c0e96d10,0x00000006cb900000)
Metaspace used 3046K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 334K, capacity 388K, committed 512K, reserved 1048576K性能
查看此GC日志前咱们先来简单了解下各参数的含义,[GC (System.gc()和[Full GC (System.gc()说明了此次垃圾收集的停顿类型,而不是来区分新生代GC和老年代GC的。 [Full GC (System.gc() 说明此次GC发生了STW,STW也就是Stop the World机制,意思是说在执行垃圾收集算法时,只有GC线程在运行,其余的线程则会所有暂停,等待GC线程执行完毕后才能再次运行。
PSYoungGen表明新生代,ParOldGen表明老年代,Metaspace表明元空间(JDK 8中用来替代永久代PermGen)。
咱们来看日志的[GC (System.gc()),内存变化为:8028K->840K(251392K),8028K表明回收前的内存大小,840K表明回收后的内存大小,251392K表明内存总大小。所以能够得知内存回收大小为(8028-840)K。这就说明JDK8的HotSpot虚拟机并无采用引用计数算法来标记内存,它对上述代码中的两个死亡对象的引用进行了回收。学习
这个算法的基本思想就是选定一些对象做为GC Roots,并组成根对象集合,而后从这些做为GC Roots的对象做为起始点,向下进行搜索,若是目标对象到GC Roots是链接着的,咱们则称该目标对象是可达的,若是目标对象不可达则说明目标对象是能够被回收的对象,以下图所示。
测试
从上图看以看出,Obj五、Obj6和Obj7都是不可达的对象,其中Obj5和Obj6虽然互相引用,可是由于他们到GC Roots是不可达的因此它们仍旧会断定为可回收的对象,这样根搜索算法就解决了引用计数算法没法解决的问题:已经死亡的对象由于相互引用而不能被回收。
在Java中,能够做为GC Roots的对象主要有如下几种:
还有一个问题是被标记为不可达的对象会当即被垃圾收集器回收吗?要回答这个问题咱们首先要了解Java对象在虚拟机中的生命周期。
当Java对象被类加载器加载到虚拟机中后,Java对象在Java虚拟机中有7个阶段。
1.建立阶段(Created)
建立阶段的具体步骤为:
2.应用阶段(In Use)
当对象被建立,并分配给变量赋值,状态就切换到了应用阶段。
这一阶段的对象至少要具备一个强引用,或者显式的使用软引用、弱引用或者虚引用。
3.不可见阶段(Invisible)
程序中找不到对象的任何强引用,好比程序的执行已经超出了该对象的做用域。在不可见阶段,对象仍可能被特殊的强引用GC Roots持有着,好比对象被本地方法栈中JNI引用或是被运行中的线程引用等。
4.不可达阶段(Unreachable)
程序中找不到对象的任何强引用,而且垃圾收集器发现对象不可达。
5.收集阶段(Collected)
垃圾收集器已经发现对象不可达,而且垃圾收集器已经准备好要对该对象的内存空间从新进行分配时。这个时候若是该对象重写了finalize方法,则会调用该方法。
6.终结阶段(Finalized)
当对象执行完finalize法后仍然处于不可达状态时,或者对象没有重写finalize方法,则该对象进入终结阶段,并等待垃圾收集器回收该对象空间。
7.对象空间从新分配阶段(Deallocated)
当垃圾收集器对对象的内存空间进行回收或者再分配时,这个对象就会完全消失。
好了,咱们已经了解了Java对象在虚拟机中的生命周期,再来回想我方才说的问题:被标记为不可达的对象会当即被垃圾收集器回收吗?很显然是不会的,被标记为不可达的对象会进入收集阶段,这时会执行该对象重写的finalize方法,若是没有重写finalize方法或者finalize方法中没有从新与一个可达的对象进行关联才会进入终结阶段,并最终被回收。
参考资料
《深刻理解 Java 虚拟机:JVM 高级特性与最佳实践》第二版
《Java虚拟机精讲》
《HotSpot实战》
《Android应用性能优化最佳实践》
JVM 深刻笔记(3)垃圾标记算法
GC roots
Java GC - 监控回收行为与日志分析
Java:对象的强、软、弱和虚引用
JVM GC中Stop the world案例实战
Java对象的生命周期
欢迎关注个人微信公众号,第一时间得到博客更新提醒,以及更多成体系的Android相关原创技术干货。
扫一扫下方二维码或者长按识别二维码,便可关注。