JAVA 的 GC 处理
判断草死掉的两种方式:引用计数和可达性分析
可达性分析对 JAVA 比较好用的原因是 JAVA遵守这面向对象的严格要求,每个变量都被对象包裹,所以每个变量都能通过对象来进行遍历找到,最终判断他们的是否被引用;
但对于 PHP 这类面向过程下构建的面向对象,找变量引用就比较零碎了,所以主要是引用计数为主;
便捷记忆,在一片空地上,死掉的草都清理(标记清除),就会出现零碎的光秃点,随着秃点增多,将不是秃点的草都挪到新的空地(复制),这样腾出的地和秃点合在一起形成新的空地,有些草活得比较久,有些草活得短,根据查看的频次将活得短的聚合在一起,活得长的聚合在一起,就是分代算法。
Java的垃圾回收(Garbage Collection,GC)机制是一种自动管理内存的技术,它负责检测和清理不再被引用的对象,以释放内存并防止内存泄漏。在Java中,开发人员无需手动释放内存,因为这是由垃圾回收器自动完成的。Java的垃圾回收机制主要基于以下两个原则:
-
引用计数(Reference Counting):这是一种简单的垃圾回收方法,它通过跟踪每个对象的引用计数来确定对象是否可以被回收。当引用计数为零时,对象被认为是不再被引用的,可以被回收。然而,这种方法容易出现循环引用问题,因此在Java中并没有广泛使用。
-
可达性分析(Reachability Analysis):这是Java主要使用的垃圾回收方法。它从根对象(如主方法中的局部变量和静态变量)开始,通过引用链追踪对象是否可以被访问。如果一个对象不再通过任何引用链与根对象相连,那么这个对象就被认为是不可达的,可以被回收。
以下是Java中几种主要的垃圾回收算法:
-
标记-清除算法(Mark and Sweep):
- 这是最基本的垃圾回收算法。
- 首先,它通过可达性分析标记所有可达对象。
- 然后,它扫描整个堆,清理未标记的对象。
- 这个算法的问题是会产生内存碎片,可能会导致堆内存不连续,进而影响程序性能。
-
复制算法(Copying):
- 这个算法将堆内存分为两个区域:一半是已使用的区域,一半是空的。
- 它首先在已使用区域进行标记,然后将存活的对象复制到空区域,最后清理已使用区域。
- 这个算法的优点是避免了内存碎片问题,但需要额外的内存用于复制对象。
-
标记-整理算法(Mark and Compact):
- 这个算法结合了标记-清除和复制算法的优点。
- 首先,它标记可达对象,然后将它们压缩到堆的一端,清理掉堆的另一端的垃圾对象。
- 这个算法也可以减少内存碎片,但不需要额外的内存。
-
分代算法(Generational):
- 这个算法根据对象的年龄将堆分成不同的代。
- 年轻代中的对象通常具有较短的生命周期,老年代中的对象具有较长的生命周期。
- 垃圾回收频繁地发生在年轻代,而较少发生在老年代。
- 这个算法利用了对象的特性,提高了回收效率。
PHP 的 GC 处理
PHP 使用引用计数(Reference Counting)作为主要的垃圾回收机制。每个变量和对象在 PHP 中都有一个引用计数器,用于跟踪对象的引用关系。当一个变量或对象的引用计数降为零时,它就会被认为是不再被引用的,可以被垃圾回收。
以下是 PHP 引用计数垃圾回收机制的基本工作原理:
-
引用计数:每当一个变量或对象引用另一个变量或对象时,引用计数会增加1。当一个变量不再引用某个对象时,引用计数会减少1。这个计数器是实时更新的,反映了对象的当前引用情况。
-
回收不可达对象:垃圾回收器会定期检查引用计数,并找出那些引用计数为零的对象。这些对象被认为是不再被引用的,可以安全地回收内存。
-
循环引用问题:引用计数垃圾回收机制对于循环引用问题并不十分有效。循环引用是指一组对象互相引用,导致它们的引用计数永远不会降为零。为了解决这个问题,PHP 引入了周期性垃圾回收(Cycle Collector),它定期检测并处理循环引用。
-
周期性垃圾回收:周期性垃圾回收器负责检测循环引用,并释放被这些循环引用困住的内存。它会在特定的时间间隔内运行,识别并处理循环引用,从而防止内存泄漏。
Python 的 GC 处理
Python 的垃圾回收(Garbage Collection,GC)机制主要基于引用计数和循环垃圾回收两个核心原理,以自动管理内存。以下是 Python 的垃圾回收机制的详细说明:
-
引用计数(Reference Counting):
- Python 中的每个对象都有一个引用计数器,用于跟踪对象的引用关系。
- 当一个对象被引用时,其引用计数会增加1,当一个对象不再被引用时,其引用计数会减少1。
- 当引用计数降为零时,说明对象不再被任何变量或数据结构引用,可以被垃圾回收。
-
循环垃圾回收:
- 引用计数机制可以有效地处理大多数情况,但对于循环引用(两个或多个对象相互引用形成环)的情况无法处理。
- 为了解决循环引用问题,Python 引入了循环垃圾回收器(Cycle Collector)。
- 循环垃圾回收器定期检查引用关系图,找出并清理不可达的循环引用。
-
分代垃圾回收:
- Python 的垃圾回收还引入了分代垃圾回收策略,将对象分为三代:年轻代、中年代和老年代。
- 大部分新创建的对象放入年轻代,如果在几轮垃圾回收后仍然存活,它们会晋升到中年代,然后到老年代。
- 基于分代的策略利用了对象的生命周期模式,提高了垃圾回收的效率。
-
垃圾回收周期:
- Python 的垃圾回收不是持续运行的,而是在特定条件下触发。
- 垃圾回收器会在一组触发条件满足时运行,如年轻代中对象的数量超过阈值、内存分配请求失败等。
- 当触发条件满足时,垃圾回收器会进行一轮或多轮垃圾回收,清理不可达对象。
-
gc
模块:- Python 提供了一个名为
gc
的模块,用于手动控制和配置垃圾回收机制。 - 开发人员可以使用
gc
模块中的函数来手动触发垃圾回收、禁用垃圾回收或配置垃圾回收参数。
- Python 提供了一个名为
PHP GC 和 Python GC 的差异
PHP 和 Python 是两种不同的脚本语言,它们的垃圾回收(GC)算法和策略存在一些差异。以下是 PHP 和 Python 的 GC 算法之间的主要区别:
PHP 的 GC 算法:
-
引用计数:PHP 主要使用引用计数来管理内存。每个变量和对象都有一个引用计数,当引用计数变为零时,变量或对象就被回收。这个方法简单且实时,但容易出现循环引用问题。
-
周期性垃圾回收:为了解决循环引用问题,PHP 引入了周期性垃圾回收(Cycle Collector)。周期性垃圾回收器会定期检查对象之间的引用关系,以便释放不再使用的内存。这是 PHP 针对循环引用问题的一种补救措施。
-
Zend Memory Manager:PHP 使用 Zend Memory Manager 来管理内存。它负责内存分配和释放,以及跟踪引用计数和垃圾回收。Zend Memory Manager 可以通过配置参数来调整垃圾回收行为。
Python 的 GC 算法:
-
引用计数和循环垃圾回收:Python 同样使用引用计数来追踪对象的引用关系。但与 PHP 不同,Python 还实现了循环垃圾回收器,用于检测并处理循环引用。
-
分代垃圾回收:Python 的垃圾回收还引入了分代垃圾回收策略。Python 将对象分为三代:年轻代、中年代和老年代。新创建的对象会放入年轻代,如果经过一定数量的垃圾回收循环后仍然存活,它们会被提升到中年代,以此类推。这个策略利用了观察到的对象生命周期模式,提高了垃圾回收的效率。
-
垃圾回收周期:Python 的垃圾回收器不是定期运行的,而是基于阈值和触发条件来触发的。具体触发条件包括年轻代对象数量、年轻代空间占用率等。这个策略可以降低垃圾回收对性能的影响。
GO 语言的 GC
Go语言(或简称Golang)的垃圾回收(GC)是一种自动管理内存的机制,旨在减轻开发人员手动管理内存的负担,提高程序的可靠性和性能。Go语言的GC机制具有以下特点和原则:
-
并发垃圾回收:Go语言的GC是并发的,这意味着垃圾回收器可以在程序运行的同时执行。它不会导致应用程序停顿(Stop-The-World),因此对于高并发和实时性要求高的应用程序非常适用。
-
分代垃圾回收:Go语言的GC使用分代垃圾回收策略,将堆内存分为三代:新生代、中年代和老年代。新分配的对象通常放入新生代,存活时间较长的对象会升级到中年代和老年代。这种分代策略提高了垃圾回收的效率。
-
标记-清除算法:Go语言的垃圾回收器使用标记-清除(Mark-and-Sweep)算法。它首先标记所有活动对象,然后清除未标记的对象。这个算法能够处理循环引用并有效地回收不再使用的内存。
-
三色标记法:Go语言的GC引入了三色标记法,将对象标记为白色、黑色和灰色。黑色表示已经标记为活动对象,白色表示未标记的对象,灰色表示待处理的对象。这个方法有助于减少标记和清除的阶段。
-
垃圾回收时间控制:Go语言的垃圾回收器可以通过设置环境变量来控制垃圾回收的时间和频率。这使得开发人员可以根据应用程序的需求进行调整,以平衡性能和内存使用。
-
内存分配优化:Go语言的运行时系统还包括了一个内存分配器,它针对小对象和大对象进行了优化,以减少内存碎片和提高分配性能。
都有哪些场景会用到循环引用?
循环引用是指两个或多个对象之间相互引用,形成一个环形的引用关系。这种情况通常不是有意为之,而是一种错误或者设计不当的情况。尽管循环引用通常是不推荐的,但有一些特定的情景可能需要它们,尤其是在某些编程模型或设计中:
-
数据结构中的循环引用:在某些数据结构中,循环引用是有意为之的。例如,双向链表中的每个节点都包含对前一个和后一个节点的引用,因此它们会形成一个环形引用。这种数据结构在某些情况下非常有用。
-
图数据结构:图是由节点和边组成的数据结构,其中节点之间可能存在循环引用。图数据结构通常用于表示网络、关系和复杂的数据关联。在这种情况下,循环引用是必要的。
-
观察者模式:在观察者模式中,主题对象(Subject)和观察者对象(Observer)之间可能存在循环引用。主题对象需要通知观察者对象进行更新,而观察者对象通常也需要引用主题对象以便取消注册。这种情况下,循环引用是合理的。
-
资源管理:某些资源需要引用它们的持有者。例如,一个对象可能需要引用一个打开的文件、数据库连接或网络连接。这种情况下,资源可能会被多个对象持有,形成循环引用,以确保在没有对象引用资源时不会被关闭。
Javascript 循环引用的一个案例
class Subject {constructor() {this.observers = [];}addObserver(observer) {this.observers.push(observer);}notify(message) {this.observers.forEach(observer => {observer.update(message);});}
}class Observer {constructor(name) {this.name = name;}update(message) {console.log(`${this.name} received message: ${message}`);}
}const subject = new Subject();
const observer1 = new Observer("Observer 1");
const observer2 = new Observer("Observer 2");subject.addObserver(observer1);
subject.addObserver(observer2);observer1.subject = subject; // 主题引用了观察者
observer2.subject = subject;subject.notify("Hello, observers!");
在观察者模式中,观察者通常引用主题对象是为了实现以下目的:
-
访问主题数据:观察者需要访问主题对象的数据或状态以执行相应的操作。通过引用主题对象,观察者可以轻松地获取主题的信息。
-
响应主题的变化:观察者的主要任务是响应主题对象的状态变化。通过引用主题对象,观察者可以注册自己,并接收主题的通知,从而知道何时以及如何更新自己。
-
与主题建立松耦合关系:引用主题对象有助于观察者与主题对象之间建立松耦合的关系。观察者不需要了解主题对象的内部实现细节,只需关注主题的状态变化即可