java垃圾回收的作用和原理(java垃圾回收fialize)
导语:java基础知识总结之垃圾回收
一、如何判断对象是否可以回收
1.1引用计数法:每当有一个地方引用它时,计数器就加1,当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能在被使用的,但是如果存在循环引用,会导致对象无法被回收,如果大量存在这种现象,则会引发内存泄漏。
1.2可达性分析算法:首先要确定一系列根(GC Root)对象,可以理解为肯定不能被当做垃圾回收的对象。在垃圾回收之前,我们会对堆中的所有对象进行扫描,看每个对象是否被根对象直接或间接引用,如果是,那么这个对象就不能被回收,反之,那就可以被回收。
1.3五种引用:强引用、软引用、弱引用、虚引用以及终结器引用
所有的实线箭头都表示强引用
当没有强引用引用A2和A3时:当垃圾回收之后内存还是不够时,那么软引用A2会被回收掉;只要发生了垃圾回收,都会把弱引用A3给回收掉。软、弱引用都可以通过配合引用队列来释放自己。
虚引用和终结期引用必须配合引用队列使用。当创建ByteBuffer时,会分配一个直接内存,并且会创建一个Cleaner的虚引用,直接内存的地址会被传递给虚引用对象。这么做的原因是,当ByteBuffer没有强引用时,该对象会被垃圾回收掉,但是分配的直接内存没办法被java的垃圾回收器回收,所以当ByteBuffer被回收后,虚引用会进入引用队列,这时会有一个线程定时去引用队列中寻找是否有新入队的Cleaner,如果有,则会调用Cleaner中的clean()方法,即Unsafe.freeMemory()将直接内存释放掉,这样就保证了直接内存被正确的释放掉。
当A4对象没有强引用时,终结器引用会被放入引用队列,并且一个优先级很低的线程会查看引用队列是否有新入队的终结器引用,如果有,则调用引用的A4对象的finalize()方法,这样下次垃圾回收的时候A4对象被回收。但是由于维护该引用队列的线程的优先级很低,会导致对象的finalize()方法迟迟得不到调用,这就是为什么不推荐使用finalize()释放对象的原因。
二、垃圾回收算法
2.1标记清理
将没有被GC Root引用的对象标记出来,然后将标记的对象释放出来。这种方式的速度很快,但是缺点也很明显,就是空间不连续,产生内存碎片。
2.2标记整理
将没有被GC Root引用的对象标记出来,然后回收垃圾对象,进行整理,使得内存更紧凑。这种方式虽然速度慢,但是没有内存碎片。
2.3复制
维护两块大小相同的内存,将FROM区的GC Root引用的对象复制到TO内存,然后将FROM区的垃圾一下清空,并且交换FROM和TO的位置,即:
优点是不会产生内存碎片,但是缺点是需要维护双倍的内存空间。
三、分代垃圾回收
将需要长期引用的对象放入老年代中,其余的放入新生代中。这样是为了对不同的对象采用不同的回收算法,更加有效的对垃圾回收进行管理。
新创建的对象会放入eden区中,当eden区满了之后,会触发一次 Minor GC(会引发stop the world,因为复制算法会导致地址变化,所以会将其他用户的线程暂停,等垃圾回收结束,用户线程才会恢复运行),采用可达性分析算法,沿着GC Root引用链去找垃圾,标记成功之后,会采用复制算法,将存活的对象复制到TO区域中,且将TO中的对象的寿命+1。
做完一次复制算法之后,会将FROM和TO的位置进行交换:
当eden重新满了之后,会触发第二次Minor GC,重复上述操作,将eden区中的对象放入TO区中,并且将FROM幸存区中的对象也复制到TO区域中,将FROM区中的对象寿命+1,并且FROM和TO区交换位置。
当对象的寿命超过了阈值,默认15,则将对象放入老年代中:
当老年代内存不足时,会先触发一次Minor GC触发一次Full GC,做一次从新生代到老年代的清理。
4、垃圾回收器
4.1串行:指单线程的垃圾回收器,适合堆内存较小的个人电脑。
Serial工作在新生代,采用复制算法,SeriaOld在老年代工作,采用标记整理算法。
4.2吞吐量优先:多线程,适合堆内存较大的情况,多核CPU,让单位时间内Stop The World的时间最短。
UseParallelGC工作在新生代,采用复制算法,UseParallelOldGc工作在老年代,采用标记整理算法。
4.3响应时间优先:多线程,适合堆内存较大的情况,多核CPU,尽可能让单次的STW时间最短。
UseConcMarkSweepGC工作在老年代,采用标记清除算法,UseParNewGC工作在新生代,当UseConcMarkSweepGC并发失败时,会将老年代的垃圾回收器用SerialOld替代。
本文内容由小葵整理编辑!