文章目录
- 🍊 可达性分析算法
- 🍊 强软弱虚引用
- 🎉 强引用
- 🎉 软引用
- 🎉 弱引用
- 🎉 虚引用
- 🍊 不可达对象GC的过程
- 🎉 GC中不可达对象的回收过程
- 📝 1. 标记阶段
- 📝 2. 清除阶段
- 📝 3. 压缩阶段
- 📝 4. 回收阶段
- 📝 5. 总结
- 🍊 三色标记
- 🎉 白色标记——对象不可达
- 🎉 黑色标记——已经被访问
- 🎉 灰色标记——部分对象未被扫描
- 🎉 总结
📕我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。🌎跑过十五公里、徒步爬过衡山、🔥有过三个月减肥20斤的经历、是个喜欢躺平的狠人。
📘拥有多年一线研发和团队管理经验,研究过主流框架的底层源码(Spring、SpringBoot、Spring MVC、SpringCould、Mybatis、Dubbo、Zookeeper),消息中间件底层架构原理(RabbitMQ、RockerMQ、Kafka)、Redis缓存、MySQL关系型数据库、 ElasticSearch全文搜索、MongoDB非关系型数据库、Apache ShardingSphere分库分表读写分离、设计模式、领域驱动DDD、Kubernetes容器编排等。🎥有从0到1的高并发项目经验,利用弹性伸缩、负载均衡、报警任务、自启动脚本,最高压测过200台机器,有着丰富的项目调优经验。
📙经过多年在CSDN创作上千篇文章的经验积累,我已经拥有了不错的写作技巧。同时,我还与清华大学出版社签下了四本书籍的合约,并将陆续在明年出版。这些书籍包括了基础篇、进阶篇、架构篇的📌《Java项目实战—深入理解大型互联网企业通用技术》📌,以及📚《解密程序员的思维密码–沟通、演讲、思考的实践》📚。具体出版计划会根据实际情况进行调整,希望各位读者朋友能够多多支持!
希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!
- 💂 博客主页: 我是廖志伟
- 👉开源项目:java_wxid
- 🌥 哔哩哔哩:我是廖志伟
- 🎏个人社区:幕后大佬
- 🔖个人微信号:
SeniorRD
💡在这个美好的时刻,本人不再啰嗦废话,现在毫不拖延地进入文章所要讨论的主题。接下来,我将为大家呈现正文内容。
🍊 可达性分析算法
可达性分析算法是一种基本的垃圾回收算法,用于动态回收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 (不可达)
这个例子只有三个对象比较简单,实际上程序中可能有成千上万个对象,构建对象图和遍历对象图的过程可能比较复杂。但可达性分析算法的底层执行流程是类似的,都是建立对象图、标记根节点、遍历对象图,最后输出可达对象。
以下是可达性分析算法底层执行流程:
-
定义对象和引用:在分析程序之前,需要确定程序中所有的对象和引用。对象可以是任何类型的数据,包括简单类型(如整数和布尔值)和复杂类型(如数组和对象)。引用是指指向对象的变量或指针。
-
建立对象图:将所有的对象和引用按照它们之间的关系构建成一个对象图。对象图可以理解为一个有向图,其中节点表示对象,边表示引用。
-
标记根节点:为了开始分析,需要标记一些根节点。根节点是那些可以从程序中访问到的对象,如全局变量、局部变量和函数参数等。将这些根节点标记为已访问。
-
遍历对象图:从根节点开始遍历对象图,找到所有可达的对象。在遍历过程中,将访问过的对象标记为已访问,并记录它们的引用关系。
-
输出可达对象:遍历完成后,所有被访问过的对象都被标记为已访问,而未被访问的对象则是不可达的。可达性分析算法可以输出所有被访问过的对象,也可以输出被访问次数超过一个阈值的对象,以帮助优化程序。
以下是一个简单的例子来说明可达性分析算法的执行流程:
- 定义对象和引用:假设我们要分析以下程序:
function foo() {
let a = {prop1: "value1"};
let b = {prop2: "value2"};
a.next = b;
return a;
}
let c = foo();
在这个程序中,我们有三个对象,分别是 a
、b
和 c
,以及两个引用,分别是 a.next
和返回值 c
。
- 建立对象图:我们可以将对象和引用构建成一个对象图,其中
a
和b
是节点,a.next
是从a
到b
的边。这样,我们就得到了以下对象图:
a --> b
c
-
标记根节点:在这个程序中,根节点是
c
,因为它是从程序的起点开始访问的。 -
遍历对象图:从根节点
c
开始遍历对象图,我们可以找到所有可达的对象。首先访问c
,然后访问a
,发现它有一个指向b
的引用,于是继续访问b
。遍历完后,我们可以得到以下已访问的对象:
c, a, b
- 输出可达对象:在这个例子中,所有被访问过的对象都被标记为已访问,因此所有的对象都是可达的。如果我们要输出被访问次数超过一个阈值的对象,那么在这个例子中,所有对象的访问次数都是 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引擎中的垃圾回收器是基于标记-清除算法和三色标记算法的,可以高效地回收不再使用的内存。
总的来说,三色标记算法是一种非常优秀的垃圾回收算法,可以有效地管理内存,提高程序的性能。该算法已经被广泛应用于各种编程语言中,成为了内存管理的重要工具。
🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~
希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!
- 💂 博客主页: 我是廖志伟
- 👉开源项目:java_wxid
- 🌥 哔哩哔哩:我是廖志伟
- 🎏个人社区:幕后大佬
- 🔖个人微信号:
SeniorRD
📥博主的人生感悟和目标
- 🍋程序开发这条路不能停,停下来容易被淘汰掉,吃不了自律的苦,就要受平庸的罪,持续的能力才能带来持续的自信。我本身是一个很普通程序员,放在人堆里,除了与生俱来的盛世美颜,就剩180的大高个了,就是我这样的一个人,默默写博文也有好多年了。
- 📺有句老话说的好,牛逼之前都是傻逼式的坚持,希望自己可以通过大量的作品、时间的积累、个人魅力、运气、时机,可以打造属于自己的技术影响力。
- 💥内心起伏不定,我时而激动,时而沉思。我希望自己能成为一个综合性人才,具备技术、业务和管理方面的精湛技能。我想成为产品架构路线的总设计师,团队的指挥者,技术团队的中流砥柱,企业战略和资本规划的实战专家。
- 🎉这个目标的实现需要不懈的努力和持续的成长,但我必须努力追求。因为我知道,只有成为这样的人才,我才能在职业生涯中不断前进并为企业的发展带来真正的价值。在这个不断变化的时代,我必须随时准备好迎接挑战,不断学习和探索新的领域,才能不断地向前推进。我坚信,只要我不断努力,我一定会达到自己的目标。