提高Java程序性能!了解可达性分析算法、强软弱虚引用和三色标记GC的过程,避免不可达对象阻碍程序性能!

简介: 提高Java程序性能!了解可达性分析算法、强软弱虚引用和三色标记GC的过程,避免不可达对象阻碍程序性能!


🍊 可达性分析算法

可达性分析算法是一种基本的垃圾回收算法,用于动态回收Java程序中不再使用的对象,以释放占用的内存空间。在该算法中,GC Root节点是可达性分析的起点,通过遍历整个堆内存中的对象,找出所有可达的对象,然后将不可达对象标记为白色,并最终清除它们占用的内存空间。

可以作为GC Root节点的引用点有几种类型,包括虚拟机栈中引用的对象、本地方法栈中引用的对象、方法区中的静态属性引用的对象、方法区中的常量引用的对象、Java虚拟机内部的引用以及锁引用的对象等。这些引用点可以作为可达性分析的起点,遍历整个堆内存中的对象,找出所有可达的对象,最终将不可达的对象标记为白色,并清除它们占用的内存空间。

需要注意的是,可达性分析算法并不能完全避免内存泄漏,因为有些对象虽然不再被直接引用,但是还存在于某些数据结构中,或者被缓存着,这些对象也无法被回收。因此,在编写程序时,需要尽可能地减少无用的对象持有问题,及时释放不再需要的对象,避免内存泄漏的出现。

举几个例子,在Web应用中,可能会创建一些连接数据库的对象,但是请求处理完毕后,这些对象可能就不再需要了,但是由于某些原因,这些对象还被某些数据结构(如Map、List等)持有着,导致无法被回收,一直占用着内存空间,这就是内存泄漏的一种情况。

为了优化可达性分析算法的效率,可以采用增量式的可达性分析和并发标记等技术,以减小程序停顿的时间,提高程序的性能和稳定性。

通过GC Root节点作为起点,遍历整个堆内存中的对象,找出所有可达的对象,最终将不可达的对象标记为白色,并清除它们占用的内存空间。在编写程序时,需要避免无用的对象持有问题,及时释放不再需要的对象,避免内存泄漏的出现。在垃圾回收器实现中,也需要结合一些优化技术,提高可达性分析的效率,从而保证程序的性能和稳定性。

可达性分析算法是一种静态分析算法,通常用于确定程序中哪些对象是“可达”的,即哪些对象可以被程序访问到,哪些对象是不可访问的。

举个例子,假设有一个程序,其中有三个对象:对象A、对象B和对象C。对象A指向对象B,对象B又指向对象C。那么我们可以将这三个对象构建成一个对象图,其中对象A指向对象B,对象B指向对象C,如下所示:

A -> B -> C

假设程序中有一个全局变量root,它指向对象A,那么我们可以将root标记为已访问的根节点。然后从根节点开始遍历对象图,找到所有可达的对象。在这个例子中,从根节点root开始遍历,可以访问到对象A、对象B和对象C,所以它们都被标记为已访问的可达对象。而其他未被访问的对象则是不可达的,如下所示:

root -> A -> B -> C (可达)
D (不可达)

这个例子只有三个对象比较简单,实际上程序中可能有成千上万个对象,构建对象图和遍历对象图的过程可能比较复杂。但可达性分析算法的底层执行流程是类似的,都是建立对象图、标记根节点、遍历对象图,最后输出可达对象。

以下是可达性分析算法底层执行流程:

  1. 定义对象和引用:在分析程序之前,需要确定程序中所有的对象和引用。对象可以是任何类型的数据,包括简单类型(如整数和布尔值)和复杂类型(如数组和对象)。引用是指指向对象的变量或指针。
  2. 建立对象图:将所有的对象和引用按照它们之间的关系构建成一个对象图。对象图可以理解为一个有向图,其中节点表示对象,边表示引用。
  3. 标记根节点:为了开始分析,需要标记一些根节点。根节点是那些可以从程序中访问到的对象,如全局变量、局部变量和函数参数等。将这些根节点标记为已访问。
  4. 遍历对象图:从根节点开始遍历对象图,找到所有可达的对象。在遍历过程中,将访问过的对象标记为已访问,并记录它们的引用关系。
  5. 输出可达对象:遍历完成后,所有被访问过的对象都被标记为已访问,而未被访问的对象则是不可达的。可达性分析算法可以输出所有被访问过的对象,也可以输出被访问次数超过一个阈值的对象,以帮助优化程序。

以下是一个简单的例子来说明可达性分析算法的执行流程:

  1. 定义对象和引用:假设我们要分析以下程序:
function foo() {
  let a = {prop1: "value1"};
  let b = {prop2: "value2"};
  a.next = b;
  return a;
}
let c = foo();

在这个程序中,我们有三个对象,分别是 abc,以及两个引用,分别是 a.next 和返回值 c

  1. 建立对象图:我们可以将对象和引用构建成一个对象图,其中 ab 是节点,a.next 是从 ab 的边。这样,我们就得到了以下对象图:
a --> b
c
  1. 标记根节点:在这个程序中,根节点是 c,因为它是从程序的起点开始访问的。
  2. 遍历对象图:从根节点 c 开始遍历对象图,我们可以找到所有可达的对象。首先访问 c,然后访问 a,发现它有一个指向 b 的引用,于是继续访问 b。遍历完后,我们可以得到以下已访问的对象:
c, a, b
  1. 输出可达对象:在这个例子中,所有被访问过的对象都被标记为已访问,因此所有的对象都是可达的。如果我们要输出被访问次数超过一个阈值的对象,那么在这个例子中,所有对象的访问次数都是 1,因此没有超过阈值的对象。

总的来说,可达性分析算法的底层执行流程就是建立对象图、标记根节点、遍历对象图,最后输出可达对象。

🍊 强软弱虚引用

在 Java 中,垃圾回收器会负责回收无用的对象,以释放内存空间。但是,这个过程并不是完美的,如果某个对象仍然被引用,但是又不需要使用它了,那么这个对象就处于一种尴尬的状态。在这个时候,就需要引入强,软,弱和虚四种引用类型,来告诉垃圾回收器对这些对象如何处理,以确保内存的合理利用。

🎉 强引用

强引用是最常见的引用类型,也是默认的引用类型。如果一个对象具有强引用关系,那么垃圾回收器就不会回收该对象。强引用可以通过 new 关键字创建,例如:

Object obj = new Object();

在这个例子中,obj 引用了一个 Object 对象,垃圾回收器不能回收这个对象,除非 obj 被赋值为 null,或者整个程序执行完毕,obj 被销毁。

🎉 软引用

软引用是用来描述还有用,但是不必须回收的对象。如果一个对象只有软引用关系,那么当系统内存不足的时候,就会把这些对象列入回收范围,进行第二次垃圾回收。如果第二次回收之后,还是没有足够的内存,才会抛出异常。可以通过 SoftReference 类创建软引用,例如:

SoftReference<Object> obj = new SoftReference<>(new Object());

在这个例子中,obj 引用了一个 Object 对象,但是这个引用是软引用。如果系统内存不足,垃圾回收器就会回收这个对象。需要注意的是,软引用并不会立即被回收,而是在系统内存不足的时候,才会被回收。

🎉 弱引用

弱引用是用来描述这个对象还有用,但是由于没有强引用关系,只能生存到下一次垃圾回收器进行垃圾收集。如果一个对象只有弱引用关系,那么当系统进行垃圾回收的时候,无论系统内存是否充足,都会把这个对象回收掉。可以通过 WeakReference 类来创建弱引用,例如:

WeakReference<Object> obj = new WeakReference<>(new Object());

在这个例子中,obj 引用了一个 Object 对象,但是这个引用是弱引用。如果系统进行垃圾回收的时候,这个对象的强引用全部失效,那么这个对象就会被回收掉。需要注意的是,弱引用具有不确定性,也就是说,它的回收时间不可预测,可能在任何时候被回收。

🎉 虚引用

虚引用是用来描述一些被强引用关系以外,没有任何引用关系的对象。如果一个对象只有虚引用关系,那么在任何时候,都可能被垃圾回收器回收掉。虚引用的存在不会对结构造成任何影响,也无法通过虚引用来获取对象实例。可以通过 PhantomReference 类来创建虚引用,例如:

PhantomReference<Object> obj = new PhantomReference<>(new Object(), new ReferenceQueue<>());

在这个例子中,obj 引用了一个 Object 对象,但是这个引用是虚引用。在任何时候都可能被垃圾回收器回收掉,而且这个对象被回收时,会把回收的信息放入一个队列中,可以通过这个队列来判断某个对象是否已经被回收了。

需要注意的是,虚引用不是用来获取对象实例的,因为在任何时候都不可能通过虚引用来获取对象的引用。虚引用的主要作用是用来跟踪对象被垃圾回收的状态,从而在对象被销毁之前进行一些必要的清理操作。

总结一下,四种引用类型的特点分别是:

  • 强引用:在任何时候都不会被回收,只有引用关系被断开,才会被回收。
  • 软引用:只有在系统内存不足的时候才会被回收,可以用来缓存一些占用内存比较大的对象。
  • 弱引用:只要没有强引用关系,就可以被回收,用来描述一些缓存性质的数据。
  • 虚引用:不能通过虚引用来获取对象实例,只是用来跟踪对象被回收的状态,或者进行一些必要的清理操作。

下面再举一些例子来说明四种引用类型的应用场景。

强引用的应用场景

强引用最常见的应用场景就是在创建对象的时候,默认创建的引用就是强引用。这种引用关系非常紧密,只要引用关系存在,垃圾回收器就不会回收这个对象。需要注意的是,强引用可能会导致内存泄露,因为一旦这个引用关系被建立起来,就很难被断开了。

软引用的应用场景

通常情况下,软引用用来缓存一些占用内存比较大的对象。比如,我们可以将图片资源缓存在软引用中,当用户返回到之前浏览过的图片时,可以直接从软引用中取出图片,而不必重新加载。如果系统内存充足,这些缓存的图片就能够一直保存下去,等到系统内存不足的时候,就会自动被回收掉。这种方式既能保证用户体验,又能尽可能地节省内存空间,是一种比较优秀的实践方法。

弱引用的应用场景

弱引用主要用来描述一些缓存性质的数据,比如一些不经常使用的数据。例如,我们可以将某个对象的数据缓存在弱引用中,当这个对象不再被使用时,弱引用也会自动被回收掉。这种方式既能够节省内存空间,又能够避免内存泄露的问题,是一种比较常见的实践方法。

虚引用的应用场景

虚引用的主要作用是用来跟踪对象被垃圾回收的状态,从而在对象被销毁之前进行一些必要的清理操作。比如,在某些情况下,我们需要释放掉一些占用资源比较大的对象,但是在释放之前需要进行一些必要的清理工作。这个时候,就可以使用虚引用来跟踪对象被回收的状态,一旦对象被回收了,就可以调用相应的清理方法来进行必要的清理操作。虚引用还可以用来实现一些比较高级的技巧,比如对象的拷贝和序列化等。

🍊 不可达对象GC的过程

当一个对象不再被引用时,它就成为了不可达对象。不可达对象将被Java虚拟机的垃圾回收算法回收。

例如,当一个字符串变量str不再指向字符串对象"Hello"时,这个字符串对象就成为了不可达对象,它将被回收。

String str = "Hello";
str = null; //字符串"Hello"成为了不可达对象

🎉 GC中不可达对象的回收过程

📝 1. 标记阶段

当垃圾回收器开始工作时,它会遍历内存中的所有对象,并对不再被引用的对象进行标记。在标记阶段,垃圾回收器会找出所有的不可达对象,并将它们标记为可回收对象。

例如,当一个字符串变量str不再指向字符串对象"Hello"时,这个字符串对象将被标记为可回收对象。

String str = "Hello";
str = null; //"Hello"被标记为可回收对象
📝 2. 清除阶段

在清除阶段,垃圾回收器将回收不再被引用的对象所占用的内存。在Java虚拟机中,清除阶段通常采用的是标记清除算法。

例如,当一个字符串变量str不再指向字符串对象"Hello"时,这个字符串对象将被清除。

String str = "Hello";
str = null; //"Hello"被清除
📝 3. 压缩阶段

在压缩阶段,垃圾回收器将移动剩余对象,填充内存中的空洞,以便更好地利用内存空间。在Java虚拟机中,压缩阶段通常采用的是压缩算法。

例如,当一个字符串变量str不再指向字符串对象"Hello"时,Java虚拟机将会在内存中留下空洞。在压缩阶段,垃圾回收器将移动剩余对象,填充这个空洞,以便更好地利用内存空间。

String str = "Hello";
str = null; //Java虚拟机留下了空洞
📝 4. 回收阶段

在回收阶段,垃圾回收器将执行finalize()方法,以便在对象被回收时进行一些清理工作或日志记录。如果对象在finalize()方法中重新与引用链上的任何一个对象建立关联,那它将被移出“即将回收”的集合。如果对象在finalize()方法执行后仍然不再被引用,那它就会被彻底回收了。

例如,当一个对象obj不再被引用时,Java虚拟机将把它放到F-Queue的队列里面。这个队列里面会启用一个低优先级的线程,去读取这些不可达的对象,然后一个一个的调用对象的finalize方法。如果对象的finalize方法被覆盖过,被调用过,这个时候虚拟机将这两种情况都视为“没有必要执行”。

public class MyObject {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("MyObject被回收了");
    }
}
public static void main(String[] args) {
    MyObject obj = new MyObject();
    obj = null;
    System.gc(); //"MyObject被回收了"
}
📝 5. 总结

垃圾回收算法是一种自动内存管理技术,它的目的是确保在运行程序时不会发生内存泄漏和内存溢出。Java虚拟机通过垃圾回收算法回收不再被引用的对象,以便更好地利用内存空间。当对象不再被引用时,它可以被回收。垃圾回收过程可以分为标记、清除、压缩和回收4个阶段,其中回收阶段是最后一次机会,它执行的是finalize()方法,以便在对象被回收时进行清理工作或日志记录。一旦对象被回收,它就不能再被引用了。

🍊 三色标记

三色标记算法是一种常用的垃圾回收算法,它基于可达性分析的思想,用三种不同颜色的标记来表示对象的状态,帮助程序判断哪些内存可以被回收,哪些内存仍然需要使用。

🎉 白色标记——对象不可达

白色标记表示对象不可达,即程序不再使用该对象。当程序申请一块内存时,操作系统会给这块内存分配地址,并将其标记为白色。随着程序的执行,这块内存可能会被程序引用,也可能不再被引用。如果这块内存处于不再被引用的状态,则称其为垃圾内存,需要及时回收以释放空间。

例如,当程序执行完一个函数后,函数中使用的一些变量所占用的内存可能就不再需要了。此时,这些变量占用的内存就可以被标记为白色,等待垃圾回收器回收。

🎉 黑色标记——已经被访问

黑色标记表示对象已经被扫描过了,垃圾回收器已经检查了该对象与其关联的对象,并且将这些对象标记为黑色。这些对象不再需要被扫描了,因为它们已经被确认为可达对象,程序会继续使用它们。

例如,当程序遍历一个链表时,每一个节点所占用的内存会被标记为黑色。如果遍历结束后某些节点没有被标记为黑色,说明这些节点不再被程序使用,可以被回收。

🎉 灰色标记——部分对象未被扫描

灰色标记表示对象未被扫描到,但是它仍有可能被引用,因此需要继续扫描其关联的对象。在垃圾回收的过程中,黑色对象和灰色对象一起组成了活动对象,白色对象则是未被引用的垃圾对象。

例如,当程序执行一个递归函数时,每次函数调用都会生成新的内存并标记为灰色,这些内存表示还需要进一步扫描其关联的对象。随着程序的执行,一部分内存可能被标记为黑色,表示已经扫描过了,但还有一部分内存处于灰色状态,需要继续扫描。

🎉 总结

三色标记算法是一种可达性分析的垃圾回收算法,通过不同颜色的标记表示对象的状态,帮助程序判断哪些内存可以被回收,哪些内存仍然需要使用。白色标记表示对象不可达,黑色标记表示已经被扫描过了,灰色标记表示部分对象未被扫描。该算法具有效率高、精度高等优点,被广泛应用于各种编程语言中。

1.在Java中,JVM自带的垃圾回收器就使用了三色标记算法。JVM会维护一个对象的引用计数,当对象的引用计数为0时,就将其标记为白色。在垃圾回收的过程中,JVM会使用三色标记算法,将对象分为白色、黑色和灰色三种状态,并通过扫描算法来确定哪些内存可以被回收。

2.在Python中,垃圾回收器也使用了三色标记算法。Python中的内存管理是基于引用计数的,当一个对象的引用计数为0时,就将其标记为白色。在垃圾回收的过程中,Python会使用三色标记算法,将对象分为白色、黑色和灰色三种状态,并通过扫描算法来确定哪些内存可以被回收。

3.在JavaScript中,V8引擎使用了三色标记算法。V8引擎是Chrome浏览器的核心部分,用于解析和执行JavaScript代码。V8引擎中的垃圾回收器是基于标记-清除算法和三色标记算法的,可以高效地回收不再使用的内存。

总的来说,三色标记算法是一种非常优秀的垃圾回收算法,可以有效地管理内存,提高程序的性能。该算法已经被广泛应用于各种编程语言中,成为了内存管理的重要工具。


相关文章
|
1月前
|
存储 Java
【编程基础知识】 分析学生成绩:用Java二维数组存储与输出
本文介绍如何使用Java二维数组存储和处理多个学生的各科成绩,包括成绩的输入、存储及格式化输出,适合初学者实践Java基础知识。
72 1
|
14天前
|
机器学习/深度学习 算法 5G
基于MIMO系统的SDR-AltMin混合预编码算法matlab性能仿真
基于MIMO系统的SDR-AltMin混合预编码算法通过结合半定松弛和交替最小化技术,优化大规模MIMO系统的预编码矩阵,提高信号质量。Matlab 2022a仿真结果显示,该算法能有效提升系统性能并降低计算复杂度。核心程序包括预编码和接收矩阵的设计,以及不同信噪比下的性能评估。
34 3
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
65 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
17天前
|
机器学习/深度学习 算法 数据挖掘
提高时钟置换算法的性能
【10月更文挑战第25天】通过上述一种或多种方法的综合应用,可以在不同程度上提高时钟置换算法的性能,使其更好地适应各种复杂的系统环境和应用场景,提高虚拟内存管理的效率和系统的整体性能。
35 5
|
20天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
39 2
|
21天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
14 2
|
25天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
25天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
19 0
|
26天前
|
缓存 分布式计算 监控
算法优化:提升程序性能的艺术
【10月更文挑战第20天】算法优化:提升程序性能的艺术
|
1月前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
下一篇
无影云桌面