gc
引用类型
在Java中,根据对象可达性的级别和垃圾回收机制的不同,引用类型被划分为四种:强引用(Strong Reference)、软引用(Soft Reference)、 弱引用(Weak Reference)和虚引用(Phantom Reference)。这四种引用类型各自有不同的用途和垃圾回收行为。
- 强引用(Strong Reference)
强引用是Java中最常见的引用类型。如果一个对象具有强引用,那么它永远不会被垃圾回收器回收,即使内存空间不足 ,JVM宁愿抛出OutOfMemoryError
错误也不会回收这种对象。只有当引用被显式置为null或者引用的作用域结束时,对象才可能被回收。
Object obj = new Object(); // obj是一个强引用
- 软引用(Soft Reference)
软引用是一种相对较弱的引用,用来描述一些还有用但并非必需的对象。只有在JVM内存不足时,才会回收这些对象。 软引用通常用于实现内存敏感的缓存。如果垃圾回收器没有足够的内存满足内存需求,那么这些对象就会被回收。使用SoftReference
类来创建软引用。
SoftReference<Object> softReference = new SoftReference<>(new Object());
- 弱引用(Weak Reference)
弱引用的级别比软引用更弱,它用来描述非必需对象。只要垃圾回收器运行,无论JVM内存是否足够,都会回收只被弱引用关联的对象。 弱引用适合用于实现对对象的跟踪状态,而不妨碍它的回收。使用WeakReference
类来创建弱引用。
WeakReference<Object> weakReference = new WeakReference<>(new Object());
- 虚引用(Phantom Reference)
虚引用是所有引用类型中最弱的一个,一个持有虚引用的对象和没有引用差不多,随时都可能被垃圾回收器回收, 而且其不能单独使用,必须和引用队列(ReferenceQueue
)联合使用。虚引用主要用于跟踪对象被垃圾回收的活动, 即确切地知道对象何时被从内存中删除。使用PhantomReference
类来创建虚引用。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(new Object(), queue);
- 总结
这四种引用类型从强到弱依次是:强引用、软引用、弱引用、虚引用。它们各自有不同的使用场景:
- 强引用用于常规对象引用。
- 软引用适合实现内存敏感的缓存。
- 弱引用适合用于实现没有阻碍对象回收的映射(Maps)。
- 虚引用主要用于跟踪对象被回收的状态,对于普通应用程序用途不大。
Reachability analysis
可达性分析算法(Reachability Analysis)
用于确定哪些对象是可访问的,哪些对象可以被回收。这里的“可达”意味着对象可以在任何形式的引用链中,直接或间接地从根集合(GC Roots)访问到。 在可达性分析的上下文中,根集合是分析起点。根集合包括:
- 活动线程:当前活动的Java线程。
- 方法区中类静态属性引用的对象:例如Java类的引用类型静态变量。
- 方法区中常量引用的对象:例如字符串常量池里的引用。
- 本地方法栈中JNI(Java Native Interface)引用的对象:通过JNI代码引用的对象。
- 活动Java栈帧中的局部变量表引用的对象:在执行的方法中的局部变量。
- 可达性分析过程 可达性分析从根集合开始,遵循对象图中的引用链: 从根集合出发,分析处理器遍历所有引用链。 遍历对象引用:检查每个对象是否可以通过任何引用路径从根集合到达。这包括强引用、软引用、弱引用、虚引用等。
引用计数算法(Reference Counting)
引用计数法 引用计数法是另一种内存管理技术,工作原理如下: 每个对象都有一个与之关联的引用计数器。 每当有一个引用指向对象时,该对象的引用计数增加。 每当一个引用被释放时,该对象的引用计数减少。 当一个对象的引用计数为零时,意味着没有任何引用指向该对象,因此它可以被回收。
为什么HotSpot不使用引用计数法? 引用计数法虽然实现简单,且回收及时,但它有一些缺点,主要包括:
- 循环引用:如果两个或多个对象相互引用,它们的引用计数永远不会达到零,即使这些对象已经不再被其他活跃的对象或根对象所引用。这导致内存泄漏。
- 性能开销:每次引用更新都需要修改引用计数,这在引用频繁变动的环境下可能导致显著的性能开销。
并发复杂性:在多线程环境中,正确地管理引用计数变得复杂且开销较大
垃圾回收算法
垃圾回收(Garbage Collection, GC)是编程语言中自动管理内存的一个重要特性,主要用于自动回收程序不再使用的内存空间。以下是一些常见的垃圾回收算法:
标记-清除(Mark-Sweep):这是最基本的垃圾回收算法。它分为两个阶段:标记阶段和清除阶段。在标记阶段,算法遍历所有可达的对象,并标记它们。在清除阶段,算法扫描内存中的所有对象,回收那些未被标记的对象。这种方法的缺点是可能会造成内存碎片。
标记-压缩(Mark-Compact):为解决内存碎片的问题,标记-压缩算法在完成标记阶段后,不是简单地清除未标记的对象,而是将所有存活的对象压缩到内存的一端,然后清理剩余空间。这样可以保持内存的连续性,提高内存使用效率。
复制(Copying):复制算法将内存分为两半,每次只使用其中一半。在垃圾回收时,它将所有存活的对象从当前使用的内存区复制到另一半,然后清理原来的内存区。这种方法可以迅速完成垃圾回收,但是其缺点是内存利用率较低。
分代收集(Generational Collection):这种方法基于这样一个观察:大多数对象的生存时间都很短。因此,它将对象分为几代。新创建的对象放在年轻代,经过多次回收依然存活的对象会被移到老年代。通常年轻代使用复制算法,老年代使用标记-清除或标记-压缩算法,以此来提高垃圾回收的效率。
g1垃圾回收器 (Garbage-First Garbage Collector)
G1 GC(Garbage-First Garbage Collector)是 JDK 8 中的一种高级垃圾收集器,它结合了多种算法来优化垃圾收集过程。G1 GC 的主要算法和特点包括:
1. 分区堆(Region-Based Heap)
G1 GC 将堆分为多个固定大小的区域(Region),每个区域可以独立分配给不同的代(年轻代或老年代),并且垃圾收集在这些区域内进行。
2. 全局并发标记(Global Concurrent Marking)
G1 GC 使用全局并发标记阶段来识别存活对象。这一阶段与应用程序线程并发执行,尽量减少停顿时间。
- 初始标记(Initial Marking): 这一步标记从根集(GC Roots)可达的对象,通常伴随一次小垃圾收集 (Minor GC)。
- 根区域扫描(Root Region Scanning): 在年轻代回收过程中扫描根区域,并标记这些区域中的对象。
- 并发标记(Concurrent Marking): 并发进行对象图的遍历,标记所有可达对象。
- 最终标记(Final Marking): 完成标记过程,处理在并发标记阶段剩余的引用变动。
- 清理(Cleanup): 识别和回收垃圾最多的区域。
3. 复制算法(Copying Algorithm)
在年轻代的垃圾收集中,G1 GC 使用标记-复制算法,将存活对象从一个区域复制到另一个区域,释放被复制对象之前的区域。这种算法提高了内存分配和回收的效率。
4. 压缩(Compaction)
G1 GC 在回收老年代时,会对选中的区域进行压缩,将存活对象集中到少数区域,从而减少内存碎片。这一步通常在并发标记阶段之后进行。
5. 暂停预测(Pause Prediction)
G1 GC 通过分析之前的垃圾收集过程来预测和控制每次垃圾收集的停顿时间。它优先选择回收垃圾最多的区域,同时考虑停顿时间的预算。
6. 并行和并发(Parallel and Concurrent Execution)
G1 GC 利用多线程进行大多数垃圾收集工作,包括标记和复制阶段,以提高垃圾收集的效率和吞吐量。同时,许多标记阶段的工作与应用线程并发执行,减少了停顿时间。
G1 GC 的设计目标是提供良好的吞吐量和响应时间性能,适用于多处理器大内存的服务器应用。通过分区和多线程的方式,G1 GC 能够有效管理内存,提高应用程序的性能和响应速度。