简单聊一下,JVM的垃圾回收机制
一.首先先确定JVM中究竟那些区域会先被回收。
熟悉JVM的人应该都清楚,JVM的内存区域大概分-本地方法栈,虚拟机栈,程序计数器,堆内存,方法区,5块空间-本地方法栈,虚拟机栈,程序计数器都是随着线程的生存毁灭而存在,所以并不需要进行垃圾回收由此得知需要进行垃圾回收的只有堆内存,方法区,下面我们简单介绍下垃圾回收算法,以及什么时候会触发垃圾回收.
二.确定了是堆内存和方法区的对象需要被垃圾回收,那具体那些对象会被垃圾回收呢
-
引用计数法
所谓引用计数法其实就是,给每一个对象加上一个计数器,如果有引用这个对象的话计数器就会+1,如果少了一个引用的话计数器就会-1,当计数器=0的时候,这个对象就会被进行GC,但是这种引用计数法是有缺陷的,举个栗子.
public class MyTest {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
# AB对象相互引用,这时候引用计数器的值永远不会为0
object1.object = object2;
object2.object = object1;
#哪怕这里已经不在引用
object1 = null;
object2 = null;
#gc也不会把对象进行回收
System.gc();
}
}
class MyObject {
MyObject object;
}
根据上面的栗子我们可以看到引用计数器的方式,其实是有循环引用这种问题没有解决的.那我们看下一种
-
可达性分析算法
如此所见从根节点GC ROOT下一次查找是否有相对应的引用,就是可达性分析算法,剩余的节点就是会被认为是没有被引用的对象,就会被垃圾回收
在Java语言中,可作为GC Roots的对象包括下面几种:
a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
b) 方法区中类静态属性引用的对象;
c) 方法区中常量引用的对象;
d) 本地方法栈中JNI(Native方法)引用的对象。
那可达性分析算法的出现肯定就是解决掉了对象间循环引用的问题,其实你了解可达性分析算法就清楚,object1 object2虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象.
清楚了那些内存空间需要被GC,那些对象会被GC之后我们就可以了解下简单的垃圾清除算法(GC)了
三.垃圾回收算法
-
引用计数法
依旧还是刚才讲的引用计数法,给对象分配一个计数器,当计数器为0的时候会被垃圾回收,需要注意的是:引用计数法很难解决对象之间相互循环引用的问题,主流Java虚拟机没有选用引用计数法来管理内存。
-
标记清除法
使用可达性分析算法,从GC ROOT中依次查找需要被收集的对象之后给上一个标记,然后在触发GC的时候对这些已经标记的算法进行回收
理解起来是很简单,但是标记清除法也是有问题的,会产生内存碎片
-
复制算法
相当于丢2块内存空间from空间和to空间
先标记出form空间中需要回收的对象
然后将form中的需要继续存储的对象整理好(这个整理包括内存碎片的整理)之后丢到to空间里,之后清理掉from空间里面的垃圾,再把from空间和to空间交换位置,就完成了GC
缺点就是需要开辟两个内存空间,会消耗内存比较大.
-
标记整理算法
我们上面说了复制算法占用了2片内存空间,消耗内存,为了解决这个问题出现了标记整理算法, 首先就是先把需要收集的垃圾打上标记
之后把可以用的对象都丢到前面去对内存进行一个整理,这样我们会发现内存更加紧凑,不会出现内存碎片的问题
标记整理算法的问题就是涉及到对象之间的移动,对于GC的效率肯定是一个降低.
四.JVM的垃圾回收算法
JVM的垃圾回收其实用了分代垃圾回收法,通俗意义上来说就是每一个区域一种垃圾回收,新生代采用赋值算法,老年代采用标记整理算法.
当我们创建一个新的对象时,那么这个对象默认就会使用 伊甸园 这块空间,接下来可能会有很多对象被创建,所以也会分配到 伊甸园 中。而随着对象创建,内存逐渐增加,当内存不够时,若再想往 伊甸园 中添加对象,这时就会触发一次 垃圾回收。
新生代的垃圾回收一般称为:Minor GC ,而 Minor GC 触发后,就会采用 可达性分析算法 沿着 GC Root 引用链去 伊甸园 中查找,看这些对象有用或者可以被当成垃圾回收,即先 标记,标记完成后,就会采用 复制 算法,把存活的对象复制到 幸存区To ,而 复制到幸存区To的对象,寿命就会加1,而至于 伊甸园 中的对象,就可以全部被当成垃圾回收。
但我们知道,完成一次 复制 算法后,From 和 To 的位置就会互换,但内存空间不会变,即只是交换位置。这就是第一次垃圾回收产生的效果
完成第一次垃圾回收后,此时 伊甸园 内存空间足够了,又可以往里面添加对象了
又过了一段时间,此时 伊甸园 的内存空间又满了,又需进行 第二次垃圾回收 ,第二次垃圾回收,除了要把 伊甸园 存活的对象找到以外,还需在 幸存区To 中判断有无需要继续存活的对象,即 幸存区To 中的对象也有可能在第二次垃圾回收中被回收,与第一次垃圾回收类似,把存活的对象复制到 幸存区To ,而 复制到幸存区To的对象,寿命就会加1,而至于 伊甸园 中的对象,就可以全部被当成垃圾回收。且完成一次 复制 算法后From 和 To 的位置需要互换
但 幸存区 中的对象不会一直存在,当超过一定寿命时(默认 15 ),就会把该对象存到 老年代 一个冷知识为什么默认为15? 对象的GC年龄肯定和对象相关,信息肯定保存在对象的某块区域,我们平时看不到是因为Java对开发者屏蔽了一些数据。
我们平时写代码,编写的只是对象的实例数据,但其实Java对象除了自身的实例数据外,还包括头信息和对齐字节,如下图所示:
对象的GC年龄就保存在对象的头信息里,除此之外,头信息还记录了对象的锁标记,大家常常说的“Java锁的是对象而不是代码”就是这个道理,上锁修改的是头信息中的锁标记。
对象的头信息内存分配不同的JVM实现不一样,一般来说32位占8字节,64位占16字节(开启压缩指针占12字节)。
因为Object Header采用4个bit位来保存年龄,4个bit位能表示的最大数就是15!
但当 老年代 与 新生代 内存空间同时不足时,这时就会触发一次 Full GC ,进行 老年代 的垃圾回收,此时就会完成一整轮垃圾回收,从新生代到老年代
五.面试题
1.Gc是什么时候触发的
Minor GC
-
轻GC一般就是新的对象在进入对内存发现伊甸园区域已经没有内存创建对象的时候就会触发
Full GC
-
老年代被写满 -
方法区被写满 -
自己使用System.gc()
2.对象一旦被创建就一定会在新生代嘛
不是的,占用比较大的内存对象会直接被创建在老年代
3.JVM的垃圾回收机制
-
JVM采用了分代垃圾回收法 -
新生代采用复制算法,划分了伊甸园区,FORM区TO区,利用两片内存空间之间,会将需要回收的垃圾做好标记之后,把需要继续残留的对象丢到TO区之后对FORM区中的垃圾进行清理之后,交换FORM和TO区的位置.(缺点就是需要2片的内存空间,占用的内存比较大) -
老年代采用标记整理法,在一片内存空间内利用标记清除法,标记需要清理的垃圾后,将不需要清理的对象都丢到老年代的前面空间去,之后对于后面的空间进行垃圾清理(缺点就是需要对对象进行一个移动,效率相对来说比较低)
原文始发于微信公众号(闯sir9):GC!!!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/20683.html