JVM垃圾回收机制

一、java垃圾回收机制的存在意义

C++的程序员在new出来一个新的对象时,其实是一个很痛苦的事,因为程序员需要对这个对象的生死存亡负起重要的责任,当一个对象在使用完之后,如果没有及时释放,那么就会造成严重的内存泄漏问题。java在引入了垃圾回收机制后,意在让java程序员可以不用去对这些对象负责,在不需要的时候,可以由java的垃圾回收线程去回收这些对象,避免内存泄漏。那么问题来了,我们是否可以任意的创建对象,然后不去理会后续处理,单纯的依赖gc呢?这是后话

二 jvm是如何判断一个对象可以被GC回收的

jvm判断一个对象是不是可以回收,最核心的判断就是:这个对象有没有被有效引用。jvm现在提供两种核心算法来判断一个对象是否处于可回收状态,分别是:
1 引用计数法
引用计数法简单来说,就是jvm在每个对象的实例中都有一个引用计数,当这个对象被创建,并且被一个变量所引用的时候,那么就讲这个引用计数+1,当这个对象实例的某个引用超过了生命周期或者被设置为一个新值时,这个对象的引用计数就减1。那么当jvm的gv启动的时候,就会回收所有少于1的对象。
引用计数法可以很快的执行gc,不会长时间的中断程序的执行,但是这会带来一个经典的问题:对象相互引用的问题。出现循环引用的对象,采用引用计数法,是不会被gc所回收的。
2 根搜索算法
jvm把所有的对象的引用关系看成一张图,也就是说从GCRoot开始,寻找对应的引用节点,然后找到这些引用节点的下一个引用节点,经过遍历之后,没有被GCRoot所引用的节点,都会被回收。

根搜索算法可以很好的解决循环引用不能被回收的bug
java中可以被当成GCRoot的对象有:
1 方法区中的静态属性引用的对象
2 方法区中常量引用的对象
3 虚拟机栈和本地方法栈引用的对象

三 jvm的垃圾回收算法

在上面我们知道了,jvm是如何判断一个对象时可以被回收的,那么,接下来jvm是如何在内存中操作这些可回收对象的呢?jvm为我们提供了一下几种垃圾回收算法:
1 标记清除法
jvm从根开始扫描对象,对存活的对象进行标记,标记完毕后,jvm会在次执行,把没有被标记的变量进行回收。标记清除法不需要对对象进行移动,在存活对象比较多的情况下,还是很高效的,但是这种算法会带来一个严重的问题:内存碎片过多。
2 复制算法
复制算法的核心思想是在内存中划分两个区域,如果一个区域中的内存用完之后,就把这个内存中的可达对象复制到另一块内存区域中,然后再回收当前的内存,这种好处就是可以极大的避免内存碎片过多,但是这样会造成内存的浪费,因为每次都需要划分多余的内存区域进行GC回收处理
在现代的虚拟机中,使用复制算法进行新生代的垃圾回收实现。在现代的虚拟机中,我们一般把内存分为三个区域,分别是一块比较大的Eden区域和两块比较小的Survivor区域。当e区和一块S区内存满时,GC开始回收,把e区和s区的可达对象集中起来放到另一块S区中。
如果S区的内存不够存储当前的可达对象是,那么就需要借助老年代内存区的帮忙了
3 标记整理算法
标记整理算法比较适合于老生代的内存区域。因为在这个内存区域中,可达对象比较多,如果采用复制算法,那么算法的效率就会比较低
标记整理算法的核心思想就是,在GC到来时,把内存区域中的所有可达对象向一端移动,然后清除端边界意外的内存。
4 分代回收算法
虚拟机把内存分为三个模块,分别是新生代,老生代,永久代。新生代由于存活对象比较少,所以可以采用复制算法,老生代可达对象比价多,GC触发不频繁,所以比较适合标记整理算法,永久代理论上不存在不可活状态,所以GC不会再这个内存区域触发。永久代现在的虚拟机已经废弃了
hotspot虚拟机认为,发生在新生代的对象存活,一次gc过后,只能存在最多10%的存活对象,因此,在新生代的划分可以分为一个eden区+两个survivor区。其中eden和survivor的内存大小是8:1。当jvm往新生代分配对象的时候,一般是先往eden区中分配内存。由上面我们知道,新生代一次gc过后,存活的对象都比较少,所以采用复制算法,一来可以提高速度,避免内存随便产生。二来可以将存活的对象分配到另一个survivor中。如果survivor被分配满了,那么剩余的对象就会被分配到老生代中。

其它补充

Minor GC:Minor GC也即是发生在新生代的gc,在新生代的内存堆中,GC会频繁的发生,在这个过程中,一般采用复制算法,避免大量的内存碎片的发生。
Full GC指发生在老年代的 GC,出现了 Major GC,经常
会伴随至少一次的 Minor GC。MajorGC 的速度一般会比 Minor GC 慢 10 倍以上。

虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1。对象在 Survivor 区中每熬过一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

一个对象是不是一定要等到年龄的阀值>=15的时候,才会被分配到老年代中?
答案是:否。在survivor区中,如果相对对象的年龄的内存总和大于当前survivor的一半,那么大于当前对象年龄的对象都会被移动到老年代中。

一个对象如果被置为null,是不是会被马上回收??
这个问题存在两个陷阱。
1 gc是不是发现对象为null的时候,就马上回收?并不是,gc线程是jvm自己控制运行的一个独立的线程,我们只能建议jvm马上调用gc算法,至于系统是否响应,就要看jvm自己的调度情况
2 一个对象为null,是不是gc执行的时候,一定会被回收?答案是否。gc在判断一个对象是不是需要被立马回收,回去判断这个对像的finalize()方法是否被重写。如果在finalize()方法中,我们重新将这个对象指向另一个对象,那么,gc也就不会去回收这个对象了。但是,finalize()系统只会去判断一次。如果我们再一次置空,那么,这个对象就会被gc回收掉。

你可能感兴趣的:(JVM垃圾回收机制)