1.如何判断一个对象是否可以回收?
Java虚拟机使用可达性分析算法来判断对象是否可以被回收。
可达性分析算法的基本思路是从一组称为“GC Roots”的根对象开始遍历所有对象,只有从GC Roots开始的对象可以被访问到,其他的对象都被判定为无用对象,可以被垃圾回收器回收。在Java虚拟机中,GC Roots包括:
1.虚拟机栈中引用的对象。
2.方法区中类静态属性引用的对象。
3.方法区中常量引用的对象。
4.本地方法栈中JNI(Java Native Interface)引用的对象。
通过可达性分析算法,垃圾回收器可以确定哪些对象是“存活的”,哪些对象可以被回收。在一次垃圾回收过程中,所有的“存活对象”都会被复制或移动到另一个区域,而所有的“垃圾对象”则被清理出堆内存,以便后续的对象分配。
需要注意的是,Java虚拟机中的垃圾回收算法并不是100%准确的,因为有一些特殊情况下,对象可能仍然被引用但已经不再被使用,从而被误判为“无用对象”,被垃圾回收器回收。这种情况称为“内存泄漏”,需要开发人员在程序设计中避免这种情况的发生。
2.对象有哪些引用类型?
1.强引用(Strong Reference):强引用是最普通的对象引用,它通过直接将对象的引用赋值给一个变量来创建。例如:
Object obj = new Object();
这里的 obj 就是一个强引用,只要强引用存在,垃圾回收器就不会回收该对象。即使内存不足,系统也会抛出 OutOfMemoryError 异常,而不是回收对象。
2.软引用(Soft Reference):软引用可以让对象存活更长的时间,当系统内存不足时,垃圾回收器会回收软引用指向的对象。可以通过 SoftReference 类来创建软引用。例如:
Object obj = new Object();
SoftReference<Object> ref = new SoftReference<>(obj);
obj = null;
这里的 ref 就是一个软引用,当内存不足时,垃圾回收器会回收 obj 对象,并且会在回收之前将 ref 设置为 null,以便我们在程序中判断是否已经回收了该对象。
3.弱引用(Weak Reference):弱引用的生命周期更短,当系统进行垃圾回收时,无论内存是否充足,都会回收弱引用指向的对象。可以通过 WeakReference 类来创建弱引用。例如:
Object obj = new Object();
WeakReference<Object> ref = new WeakReference<>(obj);
obj = null;
这里的 ref 就是一个弱引用,当垃圾回收器回收 obj 对象时,会回收 ref 引用。弱引用通常用于实现缓存、对象池等功能。
4.虚引用(Phantom Reference):虚引用也称为幽灵引用,它的作用是在对象被回收时收到一个系统通知。可以通过 PhantomReference 类来创建虚引用。例如:
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> ref = new PhantomReference<>(obj, queue);
obj = null;
这里的 ref 就是一个虚引用,当 obj 对象被垃圾回收器回收时,会将 ref 加入到 queue 队列中,程序可以通过队列来获取通知。虚引用通常用于管理直接内存等资源,需要手动释放。
3.有哪些基本的垃圾回收算法?
标记-清除算法(Mark-Sweep Algorithm):该算法分为标记和清除两个阶段。首先,垃圾回收器标记所有还存活的对象,然后清除所有未标记的对象。该算法的缺点是会产生内存碎片,导致空间不足。
优点:
实现简单,易于理解。
缺点:
会产生内存碎片,可能会导致频繁的垃圾回收和性能下降。
因为回收后会产生内存碎片,所以需要采取其他措施来避免内存碎片。
复制算法(Copying Algorithm):该算法将可用的内存空间分为两部分,每次只使用其中一部分。当该部分空间满时,将存活的对象复制到另一部分空间,然后清空原空间。该算法不会产生内存碎片,但是需要两倍的空间。
优点:
不会产生内存碎片,可以有效地避免频繁的垃圾回收和性能下降。
实现简单,复制过程也很快。
缺点:
需要两倍的内存空间,对于大对象的复制会影响性能。
需要在复制过程中维护对象的引用关系,会增加一定的开销。
标记-整理算法(Mark-Compact Algorithm):该算法分为标记和整理两个阶段。首先,垃圾回收器标记所有还存活的对象,然后将存活的对象移动到一端,然后清除边界以外的对象。该算法不会产生内存碎片,但是需要移动存活对象,可能会影响性能。
优点:
不会产生内存碎片,可以有效地避免频繁的垃圾回收和性能下降。
对于存活对象比较多的场景,标记-整理算法比标记-清除算法更适合。
缺点:
需要移动存活对象,可能会影响性能。
实现比较复杂,需要维护对象的引用关系和内存地址等信息。
分代算法(Generational Algorithm):该算法将堆内存分为多个代,一般分为新生代和老年代两个代。新生代中的对象通常存活时间很短,老年代中的对象通常存活时间较长。根据对象存活时间的不同,采用不同的垃圾回收算法。新生代通常采用复制算法,老年代通常采用标记-清除或标记-整理算法。
4.分代收集算法和分区收集算法区别?
分代收集算法是根据对象的生命周期划分不同的代,根据不同代采用适合的垃圾回收算法,如新生代使用复制算法,老年代使用标记-清除或标记-整理算法。
分区收集算法是将整个堆空间分为不同的区域,每个区域独立管理,可以针对某个区域进行垃圾回收,如G1垃圾回收器就是基于分区收集算法实现的。
5.什么是Minor GC、Major GC、Full GC?
Minor GC、Major GC、Full GC是垃圾回收的三个阶段,也称为新生代GC、老年代GC和全堆GC。
Minor GC:指的是对新生代进行垃圾回收的过程,通常采用复制算法,把存活的对象复制到另一个幸存区域,并清空当前区域。
Major GC:指的是对老年代进行垃圾回收的过程,因为老年代的对象较多,所以采用标记-清除或标记-整理算法,清理过程相对复杂,所以速度比Minor GC慢。
Full GC:指的是对整个堆空间进行垃圾回收的过程,包括新生代和老年代,通常在进行Full GC时,应用程序需要暂停,因为Full GC的时间比较长,影响系统的性能。
Minor GC和Major GC通常是并行的,而Full GC则通常是串行的,也可以并行执行。
6.JVM内存分配策略?
对象优先在Eden区分配。
新创建的对象会被分配到Eden区,如果Eden区没有足够的空间进行分配,则会触发一次Minor GC。经过Minor GC后仍然存活的对象将被移动到Survivor区。如果Survivor区没有足够的空间,则将对象转移到老年代中。
大对象直接进入老年代。
如果一个对象的大小超过了一个阈值,通常为Eden区和Survivor区的一半大小,那么它就会被直接分配到老年代中,避免在Eden区和Survivor区之间频繁移动。
长期存活的对象进入老年代。
如果一个对象经过多次Minor GC仍然存活,那么它将被移动到老年代中。
动态对象年龄判定。
Survivor区有两个,一个是From区,一个是To区,每次Minor GC后,存活下来的对象会从From区移到To区,年龄+1。当一个对象的年龄达到一定的阈值时,就会被移动到老年代中。
空间分配担保。
在发生一次Full GC之前,JVM会先检查老年代最大可用连续空间是否大于新生代所有对象的总空间,如果是,则执行Minor GC,否则会执行Full GC。如果老年代最大可用连续空间不足以容纳新生代对象,但是新生代的对象已经被转移到老年代中,那么JVM会执行一次Full GC来清理整个堆空间。
7.什么情况下会触发Full GC?
Full GC(Full Garbage Collection)是一种全堆垃圾回收,会清理整个堆空间,包括新生代和老年代。相比于Minor GC和Major GC,Full GC的代价更大,通常会导致应用程序的停顿。
下面是触发Full GC的常见情况:
老年代空间不足:当老年代中的空间不足以容纳新的对象时,会触发Full GC,以释放未被使用的对象并扩展老年代的空间。
显式调用System.gc():虽然调用System.gc()不能确保会立即执行Full GC,但是它会增加Full GC被执行的机会。通常情况下,JVM会忽略这个方法调用。
永久代(在JDK8及以前的版本中)或元空间(在JDK8及以后的版本中)空间不足:如果JVM中的元数据(如类定义、方法定义等)过多,会导致永久代或元空间不足,从而触发Full GC。
分配担保失败:在Minor GC时,如果老年代的连续可用空间不足以容纳新生代中所有的存活对象,那么就需要进行Full GC,以释放未被使用的对象并扩展老年代的空间。
需要注意的是,Full GC会导致应用程序停顿,因此在开发和部署应用程序时需要避免过于频繁的Full GC。
8.Hotspot中有哪些垃圾回收器?
Hotspot是JVM中最常用的实现之一,它包含了多个垃圾回收器,可以根据应用程序的需求选择不同的垃圾回收器来实现最优的性能。
下面是Hotspot中常见的垃圾回收器:
Serial收集器:是一种单线程的垃圾回收器,适用于小型应用程序或客户端应用程序。它在新生代使用复制算法,在老年代使用标记-整理算法。
Parallel收集器:是一种多线程的垃圾回收器,适用于中型或大型应用程序。它在新生代使用复制算法,在老年代使用标记-整理算法。
CMS收集器:是一种并发的垃圾回收器,适用于需要低停顿时间的应用程序。它在新生代使用复制算法,在老年代使用标记-清除算法。它不会等待所有的存活对象都被标记,而是在标记过程中就开始回收垃圾对象。
G1收集器:是一种面向服务端应用程序的垃圾回收器,适用于大型堆空间和多核处理器。它将堆内存分为多个大小相等的区域,每个区域可以是Eden、Survivor或Old区。它使用复制算法来收集Eden和Survivor区,使用标记-整理算法来收集Old区。
ZGC收集器:是一种低延迟的垃圾回收器,适用于需要大堆空间和短暂的停顿时间的应用程序。它采用了分代垃圾回收器的思想,但同时也支持全堆垃圾回收。它在收集垃圾时可以与应用程序并发执行,以减少停顿时间。