GC(垃圾回收)
GC(垃圾回收)
GC,Garbage Collection,是JVM的重要组成部分,短短一篇博客只是管中窥豹,如果想深入学习GC和JVM,建议研读周志明老师的的《深入理解Java虚拟机: JVM高级特性与最佳实践》
1.GC
GC,Garbage Collection。是Java中的垃圾回收器。众所周知,Java是借鉴的C++,它摈弃了C++中一些繁琐容易出错的东西。其中有一条就是这个GC。在C/C++程序中,程序员在内存中主动开辟一段相应的空间来存值。由于内存是有限的,所以当程序不再需要使用该内存空间时,就需要销毁对象并释放其所占用的内存资源,好重新利用这段空间。在C/C++中,释放无用内存空间的事情需要由程序员自己来处理。就是说当程序员认为空间没用了,就手动地释放其占用的内存。但是这样显然非常繁琐,如果有所遗漏,就可能造成资源浪费甚至内存泄露。当软件系统比较复杂,变量多的时候程序员往往就忘记释放内存或者在不该释放的时候释放内存了。
有了GC,程序员就不需要再手动的去控制内存的释放。当Java虚拟机发觉内存资源紧张的时候,就会自动地去清理无用对象(没有被引用到的对象)所占用的内存空间(这里的说法略显粗略,事实上何时清理内存是个复杂的策略)。如果需要,可以在程序中显式地使用System.gc() / System.GC.Collect()来强制进行一次立即的内存清理。Java提供的GC功能可以自动监测对象是否超过了作用域,从而达到自动回收内存的目的,Java的GC会自动进行管理,调用方法:System.gc() 或者Runtime.getRuntime().gc();
2.需要GC的内存区域
在JVM中,栈、本地方法栈、程序计数器生命周期都是和线程同步(随线程生,随线程灭)。我们的垃圾回收主要集中于Java的堆和方法区中,其中几乎所有的GC都在堆中。
3.判断GC对象的方法
需要进行回收的对象是已经没有存活的对象,判断一个对象是否存活有两种方法:引用计数和可达性分析。
1.引用计数
每个对象都有一个引用计数属性,新增一个引用时计数加1,释放一个引用是计数减1,当计数为0时可以回收。此方法比较简单,并且可以解决对象循环引用问题。
2.可达性分析(Reachability Analysis)
从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,是不可达对象。
在Java语言中,GCRoot包括:
虚拟机栈中引用的对象
方法区中类静态属性实体引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象
4.GC分类和触发
1.GC分类
GC分为轻GC和重GC。
轻GC又被称为minor GC、普通GC;
重GC又被称为Major GC、Full GC和全局GC;
2.GC触发时间
轻GC触发条件:当堆里面的伊甸园区(Eden Area)内存空间不足。
重GC触发条件:老年代空间不足;方法区空间不足;幸存区空间不足;调用System.gc方法时。
5.GC常见算法
GC常见算法:引用计数法、复制算法、标记-清除法、标记压缩法、标记-清除-压缩法、分代收集算法。
目前主流JVM(HotSpot)采用的是分代收集算法。
1.引用计数法
引用计数算法很简单,它实际上是通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,那么它的引用计数就减1,当该对象的引用计数为0时,那么该对象就会被回收。
引用计数法跟其他垃圾回收算法不同,其他垃圾回收都是在为新对象分配内存空间时由于内存空间不足而触发的,而且垃圾回收是针对整个堆中的所有对象进行的。而引用计数垃圾回收机制不一样,它只是在引用计数变化为0时即刻发生,而且只针对某一个对象以及它所依赖的其它对象。所以,我们一般也称呼引用计数垃圾收集为直接的垃圾收集机制,而前面三种都属于间接的垃圾收集机制。
优点:简单
缺点:计数器本身也有内存消耗;无法处理环形数据数据问题。
环形数据问题
即如果有两个对象相互引用,那么这两个对象就不能被回收,因为它们的引用计数始终为1。这也就是我们常说的“内存泄漏”问题。比如下图展示的将p变量赋值为null值后所出现的内存泄漏。
2.复制算法
堆内存中的新生代中的伊甸园区内存不足的时候,就会触发轻GC,存活下来的对象会存入幸存区。幸存区分为幸存0区和幸存1区,也可以称为幸存from区和幸存to区,这两个区域是动态变化的。(记住:幸存to区一定是空的)
当回收时,会将伊甸园区幸存下来的对象和幸存区存活的对象(幸存from区)都复制幸存to区,然后原来的幸存from区变成了现在的幸存to区(幸存to区一定是空的),原来的幸存to区变成了现在的幸存from区。
优点:没有内存碎片
缺点:浪费了幸存区一半内存空间
假设对象100%存活的话(极端情况)--》OOM,所以复制算法比较适合对象存货度比较低的区域,也就是新生区。
3.标记-清除算法
顾名思义,标记-清除算法分为两个阶段,标记(mark)和清除(sweep)。每个对象都存储一个标记位,标记阶段,进行可达性分析,将对象标记为存活和死亡两种状态;在清除阶段,从头到尾进行线性的遍历,将死亡的对象进行清除。
优点:不需要额外空间;不改变对象位置。
缺点:会产生内存碎片;两次扫描浪费时间。
4.标记-压缩算法
标记-压缩法是标记-清除算法的一个改进版。在标记阶段,该算法也会将存活的对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。
优点:不会产生大量碎片空间;
缺点:存活对象过多会导致效率降低;改变对象位置;
5.标记-清除-压缩算法
使用几次标记清除之后会产生大量碎片空间,然后使用标记压缩算法去除碎片空间。
6.分代收集算法
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配,所以可以使用标记-整理和标记-清除结合使用。
6.参考文献
1.参考文献
感谢:遇见狂神说
Java GC机制详解:https://blog.csdn.net/laomo_bible/article/details/83112622
引用计数法:https://www.jianshu.com/p/1d5fa7f6035c