垃圾收集机制

121 阅读6分钟

Java的垃圾收集机制是Java运行时环境的一部分,主要负责管理内存,以回收不再使用的对象所占的内存空间。Java的垃圾收集机制有以下特点:

  1. 自动化:Java的垃圾收集机制与其他语言(如C++, C)的主要区别是,Java编程者不需要显式地请求内存或逐一释放不再使用的对象,这一工作由Java虚拟机(JVM)的垃圾收集器(GC)自动完成。

  2. 停止-复制:某些类型的GC会暂停应用程序执行,将所有活动对象从一部分内存复制到另一部分,然后释放原始内存区域。这种方法称为停止-复制。

  3. 标记-清除:大部分GC采用的方法,首先通过从根对象(堆上的静态字段,局部变量,JNI引用)开始跟踪引用,找出所有可达(即正在使用或可能将来使用)的对象,这些对象称为标记对象,与之相反,未标记的对象则视为垃圾。

  4. 分代收集:Java堆内存分为新生代和老年代。新创建的对象首先在新生代分配内存,当新生代满时,GC会将存活下来的对象移动到老年代。新生代采用复制算法,而老年代通常采用标记-清除-整理算法。

  5. 并发收集:某些类型的GC为了避免停顿过程,可以与应用程序线程并发运行。例如,G1(Garbage-First)收集器和CMS(Concurrent Mark Sweep)收集器等。

  6. 适时回收:JVM会根据当前系统的运行情况,比如空闲CPU时间,内存使用状况等,自动选择最佳时机进行垃圾回收,以最小化对程序性能的影响。

  7. 弱引用、软引用、幽灵引用:Java提供了用于处理特殊垃圾回收场景的类,例如WeakReference,SoftReference,和PhantomReference。

Java的垃圾收集机制在内存管理方面,大大降低了程序员的工作量,让程序员可以更专注于业务逻辑的实现,同时提高了系统的稳定性。但同样,由于其自动化的特性,对于一些对性能要求较高,和对内存使用敏感的场景,可能会需要调优以获取最佳性能。

Python的垃圾收集机制主要由两部分组成:引用计数和循环垃圾收集器。

  1. 引用计数:Python内部使用引用计数,来保持追踪内存中的对象。每一个对象都有一个计数器,当对象的引用增加时,计数会增加,当引用对象的引用减少时,计数会减少。当这个计数器归零时,该对象就会被认作是垃圾,可以被收集和回收。

  2. 循环垃圾收集器:引用计数对于解决大部分的垃圾收集问题已经足够,但是当存在引用循环的时候,引用计数就会失效。引用循环是指一个对象被创建后,将另一个对象作为其属性,并且这个对象又引用了创造它的对象,这就形成了一个引用的循环。Python通过循环垃圾收集器来处理这类问题。循环引用会被周期性的循环垃圾收集器所检测出,当两个对象互相引用,并且不再被程序的其他部分所引用时,它们就会被收集。

Python的垃圾收集器还可以通过手动方式进行调控,可以通过gc模块暴露出来的接口,来手动控制垃圾回收的行为,比如强制进行垃圾回收,以及控制循环垃圾收集器的阈值等。

Python的垃圾收集机制要求程序员对内存管理有更少的担忧,让程序员可以关注于解决实际的问题。但在一些对性能有更高需求,或者对内存使用更敏感的应用中,程序员可能需要更加理解Python的垃圾收集机制,并可能需要进行一些调节和优化来满足这些特殊需求。

JavaScript的垃圾收集机制同样基于标记清除和引用计数两种方法。

  1. 引用计数:在较早的JavaScript引擎中,垃圾收集是基于引用计数的。引用计数的工作原理是跟踪每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,那么该值的引用次数就会增加1。相反,如果包含对这个值引用的变量被其他值所取代,那么该值的引用次数就会减1。当这个值的引用次数变为0时,则说明没有办法访问这个值了,因此可以将其占用的内存空间回收回来。但是这种方式会导致循环引用无法回收的问题,因此现代的JavaScript引擎主要不再使用这种方式。

  2. 标记清除:大多数现代的JavaScript引擎采用片断勾选策略进行垃圾收集。垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(可以看做是将内存看做图,将变量视为节点)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。 然后再被剩下的所有变量都被视为准备删除的变量,因为环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带有标记的值并回收它们所占用的内存空间。

通常,垃圾收集器工作的时机是不确定的,也即不能预料到垃圾收集器将何时开始工作,但可以确定的是,如果内存中已保存的数据量超过了JavaScript引擎认为的可接受范围,那么垃圾收集器就会开始工作。

为提高性能,一些垃圾收集器采用分代收集策略,会把对象分类为新生代和老年代,新创建的对象首先在新生代分配内存,通过标记(Scavenge)算法回收,存活的对象被移至老年代,老年代的对象通过标记清除或标记压缩算法进行回收。

这样的机制让JavaScript开发者在日常构建应用的时候无需关心内存管理,可以更专注于应用逻辑的实现。