给对象添加引用计数器,只要某个对象被某个变量所引用,就给他的引用计数+1.当引用计数为0时,就认为没有变量引用它了,即可以垃圾回收。
缺点:如果两个对象相互引用,引用计数均为1,则永远无法被回收。
首先要确定根对象,即能确定不能被当成垃圾回收的对象。
对堆中的元素进行扫描,看其是否是根对象直接或间接引用的,如果是则不能被回收。
算法思想:
通过一系列成为“GC Roots”的对象为起始点,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如下图object5、6、7都是不可达的,都会被判定为可回收对象。
1.强引用
指在程序代码中普遍存在的,例如Object obj = new Object(),只要强引用存在,被引用的对象就永远不会被回收。只有强引用全部断开,该对象才能被回收。
2.软引用
当发生垃圾回收之后,依然发现内存不足的话就会回收调被软引用的对象。因为即使该对象还有用,系统会认为软引用的对象非必需、不够重要。
//TODO 软引用实例
//TODO 将不太重要的对象放入SoftReference中,使其不占用内存空间,需要的时候再读取
//TODO 如果直接使用List强引用的话不会回收,会导致内存溢出。
List> list = new ArrayList<>();
ReferenceQueue queue = new ReferenceQueue<>();
//关联了引用队列,当软引用所关联的byte[]被回收时,软引用自己也会假如到queue中
SoftReference ref = new SoftReference<>(new byte[_4MB],queue);
黑马程序员JVM完整教程,全网超高评价,全程干货不拖沓_哔哩哔哩_bilibili 详细见视频讲解。
3.弱引用
只要发生垃圾回收且该对象没有被强引用,即使内存空间足够,也会回收掉该对象。
某个软引用对象和弱引用对象在他们所引用的对象被回收之后会被放入引用队列中,方便系统来遍历引用队列,如果需要会对这些软、弱引用对象本身进行回收。
4.虚引用
虚引用对象和直接内存有关。在虚引用对象引用的对象被垃圾回收时,虚引用对象本身也会被放入引用队列中,然后通过ReferenceHandler线程定时去引用队列中看有没有新入队的Cleaner,如果有的话就会调用Cleaner中的clean()方法,clean()方法会根据前面记录的直接内存地址调用Unsafe.freeMemory()方法来释放掉直接内存,从而避免直接内存溢出。
5.终结器引用
当终结器对象引用的对象要被垃圾回收时,终结器对象就会被放入引用队列(注意:该对象没有被立刻垃圾回收),再由一个优先级很低的线程finalizehandler来查看是否有终结期引用,有的话则会找到被引用的对象并调用它的finalize()方法,等到下次垃圾回收时就会回收掉这个对象所占用的内存。
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点:1. 效率低:标记和清除两个过程的效率都不高;
2.空间问题:会产生大量不连续的内存碎片,分配较大对象时,无法找到足够的连续空间kennel会触发下一次垃圾回收
第一部分标记同2.1一样,但后续过程是让所有寸或的对象都向前一端移动,然后直接清理掉端边界以外的内存。
优点:没有内存碎片
缺点:整理过程移动对象的过程涉及动态重定位等,速度较慢。
优点:不会产生碎片;
缺点:会占用双倍的内存。
这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为“新生代”和“老年代”
新生代中,每次垃圾回收时都有大批量的对象被回收,只有少量存活,选择复制算法,只需要付出少量存活对象的复制成本即可;
老年代中,对象存活率高,没有额外空间对它进行分配担保,就必须使用“标记清除”或“标记整理”
对象首先分配在伊甸园区域,伊甸园和from区域不足时触发MinorGC,伊甸园和from存活的对象使用复制算法copy到to中,存活的对象年龄+1并交换from和to。
Minor GC会引发stop the world,暂停其他用户的线程,让垃圾回收先工作直到结束。(时间很短)
当对象寿命超过阈值时,就会晋升到老年代(最大15)
当老年代空间不足,且触发Minor GC之后空间仍不足,就会触发full GC(也会引发stop the world 且时间会更长)
底层是一个单线程的垃圾回收器,其他线程都暂停让他来完成垃圾回收;
适合堆内存较小的个人电脑。
//TODO 开启串行垃圾回收语句:
-XX:+UseSerialGC = Serial + SerialOld
//Serial工作在新生代,采用复制算法
//SerialOld工作在老年代,采用标记-整理算法
堆内存不够了,触发垃圾回收时,让各个线程在安全点停下(因为对象的地址可能会发生改变),只有一个垃圾回收线程在运行,其他线程处于阻塞状态。
多线程,适合堆内存较大的场景,需要多核CPU来支持
让单位时间内stw的时间最短
eg:单次垃圾回收需要0.2秒,一小时内发生两次,即0.2x2=0.4秒
//TODO 开启串行垃圾回收语句:
-XX:+UseParallelGC~-XX:+UseParallelOldGC
-XX:ParallelGCThreads=n //指定参与的线程数
-XX:+UseAdaptiveSizePoicy //采取自适应的策略调整新生代的大小
-XX:GCTimeRatio=ratio //调整吞吐量,垃圾回收的时间和总时间的占比 1/(1+ratio) 一般19
-XX:MaxGCPauseMillis=ms //最大暂停时间200ms
//ratio和pauseMillis负相关 要取折中
//Parallel工作在新生代,采用复制算法
//ParallelOld工作在老年代,采用标记-整理算法
多线程,适合堆内存较大的场景,需要多核CPU来支持
垃圾回收时,让单次的stw时间尽可能短
eg:单词垃圾回收需要0.1秒,一小时内发生五次,即0.1x5=0.5秒
//TODO 开启串行垃圾回收语句:
-XX:+UseConcMarkSweepGC~-XX:+UseParNewGC~SerialOld
//concurrent并发的 cms工作在老年代,有时会并发失败,就退化到SerialOld来补救
-XX:ParallelGCThreads=n~-XX:ConcThreads=threads //指定参与的线程数
-XX:+CMSInitialOccupancyFraction=percent //执行CMS垃圾回收的内存占比
-XX:+CMSScavengeBeforeRemark //
图
参考:
1.《深入理解Java虚拟机》
2.B站黑马程序员视频黑马程序员JVM完整教程