答:JVM不定时的去检测回收不可达对象。
答:就是被创建的对象没用被继续使用,但是对象在创建的时候是可达的,是否可达是根据垃圾回收机制算法进行控制的。
拓展:将对象赋值为null的时候,那么这个对象就是不可达对象,这时候调用System.gc()方法,那么就会通知GC线程进行垃圾回收,但是不是立马被回收。
拓展:重写finalize()方法,那么该对象在被回收的时候会先执行finalize()方法。
在开始之前我们详细讲一下我们在--java虚拟机基础--中提到的堆内存
其中新生代内存区又可以细分为:
我们的GC线程是怎么知道该对象是否为可达对象呢?
两种判断算法:
实现原理:每一个对象都有一个年龄,如果小于等于15岁,那么会存放在新生代里面,如果大于15那么会被存放在老年代里面。GC线程在不定时进行回收时,如果对象继续被引用,年龄会加1,如果没用被引用则年龄会减一,初始年龄为0,当年龄为0岁的时候,那么会被垃圾回收机制认为是不可达对象,则会被GC垃圾回收
缺点:该算法缺点也是致命的,因为会参数循环依赖问题,即为:当存在循环依赖的时候,这个时候引用计数法就很难知道该对象是否被引用。
举例说明:创建对象A,B,把对象A赋值给对象B,在把对象B赋值给对象A,然后在把对象A,B都赋值为null,然后调用System.gc(),通知垃圾回收,但是使用引用计数法,GC就很难知道该对象是否被引用。
实现原理:判断对象是否可达,需要判断对象是否和GC Roots是否存在引用关系,如果不存在引用关系,GC会认为是不可达对象,这个时候则会被GC进行回收。即为:循环判断是否对根对象有依赖,有则为可达对象,无则为不可达对象。(类似于树状结构图)
举例说明:如下图,user4和user6虽然存在依赖关系,但是还是会被GC回收,因为他们没有一个依赖的跟节点。
哪些对象可以作为根节点:存在方法区和堆内存中的数据对象,都可以作为根节点,这个时候依赖于他们的成员变量以及静态变量,常量,数组,线程池等都可以作为根节点。
既然有了方法知道对象是否为可达对象,那么接下来就应该将不可达对象进行回收,那么有哪些回收方式呢?
垃圾回收算法:
为什么之前会先讲解堆内存结构以及关系呢,就是因为垃圾回收的两种算法就是基于对内存关系结构进行的。
实现原理:GC会不定时的去检测是否存在不可达对象,如果对象有被引用则标记为0,如果没有引用则标记为1,当标记为1的时候就是不可达对象,则会进行垃圾回收。
缺陷:会产生内存碎片化(很抽象),效率慢
优点:对内存的占用比较少
缺陷举例说明:因为垃圾回收是存在时间限制的,加入当检测到1W+的对象都是不可达对象的时候,这个时候GC就会删除1W+的不可达对象,因为系统高负荷,肯定会有一部分在指定时间不能成功删除,但是又删除了一部分,这就导致了了那一部分的内存空间不可用。导致内存碎片化,就像是window下载一个软件,删除一部分依赖导致软件不可用,但是该软件还是会占用系统一部分空间
适用场景:老年代的回收,因为老年代比较稳定,不会发生大量删除。
这里的复制算法就跟前面的堆内存结构依赖有关系了,这里先讲解一些结构依赖关系
主流程详解:当对象先创建的时候会保存在cden区中,GC会不定时的通过垃圾回收算法进行扫描并清除不可达对象,如果cden区中的对象在GC扫描的时候发现是不可达对象,则会进行回收,如果是可达对象并且被使用次数达到一定数量的时候就会把该对象放入到survivor区中,默认是先进入S0区,然后如果survivor区域中的对象在GC检测的时候发现经常使用,并且次数达到一定数量的时候就会把该对象放入到老年代内存里面,当老年代之中的对象为不可达的时候则会进行垃圾回收。
实现原理:survivor区中存在两个区,from以及to,这里就称之为S0,S1。那么S0和S1的大小是完全一样的,当S0或S1中存在不可达对象的时候,那么GC就会把该内存空间的可达对象赋值到另外一个S空间下面,并且清空掉该内存空间中的所有对象。
缺陷:占用内存,因为存在两个同样大小的S区,并且有一个必然为空,为了让另外一个空间内存在不可达对象之后,GC好赋值其中的可达对象到该内存中
优点:效率高,类似于系统格式化,不会发生碎片化
适用场景:新生代中使用,因为新生代中回收比较频繁。不适合一个个进行回收。
JVM垃圾回收总结:
什么是不可达对象:没有被使用的对象。
堆内存结构:新生代,老年代,cden区,survivor区,from区,to区
cden区:属于新生代,刚创建的对象存放的地方。
survivor区:属于新生代,CG发现cden区的某个对象被多次使用,则会将该对象保存在survivor区
form区:属于survivor,目的是存在不可达对象则清空form区,将可达对象全部复制到to区
to区:属于survivor,目的是存在不可达对象则清空to区,将可达对象全部复制到form区
JVM判断对象是否为可达对象:引用计数发以及根搜索法
引用计数法:当GC检测的时候发现对象被使用,则会加一操作,相反减一,当为0的时候标记为不可达对象
根搜索法:通过循环判断对象是否对根节点root存在依赖关系,如果不存咋依赖关系则为不可达对象
垃圾回收算法:标记清除法和复制算法。
标记清除法:如果该对象被使用,则标记为0,没有使用则标记为1,GC会回收所有标记为1的对象
复制算法:通过利用survivor,进行批量删除
另外推荐一些文章:
堆内存结果:JVM内存结构--新生代及新生代里的两个Survivor区(下一轮S0与S1交换角色,如此循环往复)、常见调优参数
内存碎片化:内部碎片 && 外部碎片
根搜索算法:如何简单理解GC roots和 gc的标记过程。