JVM虚拟机篇(六):深入理解Java垃圾回收机制
深入理解Java垃圾回收机制
- 深入理解Java垃圾回收机制
- 一、GC是什么?为什么要GC
- (一)GC的定义
- (二)为什么要进行GC
- 二、对象什么时候可以被垃圾器回收
- (一)引用计数法判断对象可回收
- (二)可达性分析算法判断对象可回收
- 三、JVM垃圾回收算法有哪些
- (一)标记 - 清除算法(Mark - Sweep)
- (二)复制算法(Copying)
- (三)标记 - 整理算法(Mark - Compact)
- (四)分代收集算法
- 四、分代收集算法
- (一)分代的依据
- (二)各代采用的垃圾回收算法
- (三)分代收集算法的优势
深入理解Java垃圾回收机制
一、GC是什么?为什么要GC
(一)GC的定义
GC(Garbage Collection)即垃圾回收,是Java虚拟机(JVM)提供的一种自动内存管理机制。在Java程序运行过程中,会不断创建对象,这些对象会占用堆内存空间。当这些对象不再被程序使用时,GC负责自动识别并回收这些不再使用的对象所占用的内存空间 ,让这部分内存可以被后续新创建的对象重新利用。
(二)为什么要进行GC
在没有垃圾回收机制的编程语言(如C、C++)中,程序员需要手动管理内存,包括分配内存(如使用malloc
等函数)和释放内存(如使用free
函数)。手动管理内存存在诸多问题:
- 内存泄漏:如果程序员忘记释放不再使用的内存,随着程序的运行,未释放的内存会越来越多,最终导致系统可用内存耗尽,程序崩溃。例如,在一个长时间运行的服务器程序中,频繁创建对象却不释放,就会出现这种情况。
- 悬空指针:当释放了一块内存后,如果没有正确处理指向该内存的指针,后续对该指针的访问就会引发未定义行为,可能导致程序崩溃或产生难以调试的错误。
而Java的GC机制可以自动处理这些问题,减轻了程序员的负担,让程序员可以更专注于业务逻辑的实现,而不用担心内存管理的细节。同时,GC机制还能保证内存的有效利用,维持系统的稳定运行。
二、对象什么时候可以被垃圾器回收
(一)引用计数法判断对象可回收
引用计数法是一种简单的判断对象是否可回收的方法。其原理是给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器值加1;当引用失效时,计数器值减1 。当计数器值为0时,就意味着该对象不再被任何地方引用,那么这个对象就可以被垃圾回收器回收。
然而,引用计数法存在一个严重的问题——循环引用。假设有两个对象A和B,A持有B的引用,B也持有A的引用,即使它们都不再被其他外部对象引用,但由于相互引用,它们的引用计数都不为0,导致无法被回收,造成内存泄漏。所以在Java的主流垃圾回收机制中,并没有单纯使用引用计数法来判断对象是否可回收。
(二)可达性分析算法判断对象可回收
Java采用可达性分析算法来判断对象是否可被回收。该算法的基本思路是从一系列被称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时(即从GC Roots到该对象不可达),则证明此对象是不可用的,是可以被垃圾回收器回收的对象。
在Java中,可作为GC Roots的对象包括:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象:例如,在一个方法中定义的局部变量所引用的对象,如果该方法执行完毕,且局部变量不再被使用,那么这些对象可能会成为可回收对象。
- 方法区中类静态属性引用的对象:比如一个类中的静态成员变量所引用的对象,只有当类被卸载等特殊情况时,这些对象才可能不再与GC Roots可达。
- 方法区中常量引用的对象:像
String
常量池中的字符串对象,如果不再被其他地方引用,就可能被回收。 - 本地方法栈中JNI(即Native方法)引用的对象 :当本地方法执行完毕且相关引用不再有效时,对应的对象可能会被回收。
三、JVM垃圾回收算法有哪些
(一)标记 - 清除算法(Mark - Sweep)
- 算法原理
- 标记阶段:首先从GC Roots开始,通过可达性分析算法,标记出所有仍然存活的对象。
- 清除阶段:在标记完成后,对堆内存中未被标记的对象(即死亡对象)进行回收,释放它们所占用的内存空间。
- 优点
实现简单,不需要对对象进行移动等复杂操作,在垃圾回收的实现上相对容易理解和编码。 - 缺点
- 内存碎片化:由于被回收的对象的内存空间是分散释放的,随着多次垃圾回收,会产生大量不连续的内存碎片。当需要分配较大对象时,可能无法找到足够大的连续内存空间,即使总的空闲内存足够,也会导致内存分配失败,进而引发Full GC等操作。
- 效率问题:标记和清除两个阶段的效率都不高,尤其是在对象数量较多的情况下,标记和扫描整个堆内存的开销较大。
(二)复制算法(Copying)
- 算法原理
将堆内存划分为两块大小相等的区域,每次只使用其中一块。当这一块内存用完时,就将还存活的对象复制到另一块未使用的区域中,然后将使用过的那一块内存空间一次性全部清理掉。 - 优点
- 效率高:复制算法只需对存活对象进行复制操作,相比标记 - 清除算法,减少了标记和扫描死亡对象的时间开销,在对象存活率较低的情况下,回收效率非常高。
- 无内存碎片化问题:因为每次都是整体清理一块内存区域,然后在另一块连续的区域上进行对象分配,所以不会产生内存碎片化问题。
- 缺点
- 内存利用率低:由于需要将堆内存划分为两块,实际可用的内存空间只有原来的一半,造成了较大的内存浪费。在现代Java虚拟机中,一般不会将整个堆都采用复制算法,而是在新生代等对象存活率较低的区域采用改进的复制算法。
(三)标记 - 整理算法(Mark - Compact)
- 算法原理
- 标记阶段:与标记 - 清除算法类似,从GC Roots开始,通过可达性分析算法标记出所有存活的对象。
- 整理阶段:在标记完成后,将所有存活的对象向一端移动,然后直接清理掉边界以外的内存空间。
- 优点
- 解决内存碎片化问题:通过对象移动,将存活对象整理到连续的内存空间中,避免了内存碎片化,使得后续内存分配更加高效。
- 相对较高的内存利用率:不像复制算法那样将内存空间减半,标记 - 整理算法在保证内存连续的同时,提高了内存的利用率。
- 缺点
- 效率较低:在整理阶段,需要移动存活对象,涉及对象地址的更新等操作,这些操作的开销较大,尤其是在对象数量较多的情况下,会降低垃圾回收的效率。
(四)分代收集算法
分代收集算法并不是一个全新的算法,而是根据对象存活周期的不同将内存划分为不同的区域,然后针对不同区域的特点采用不同的垃圾回收算法。这是目前Java虚拟机普遍采用的垃圾回收策略。
四、分代收集算法
(一)分代的依据
在Java程序运行过程中,对象的存活时间是有明显差异的。大多数对象的存活时间都很短,比如局部变量所引用的对象,在方法执行完毕后就不再被使用;而有些对象的存活时间较长,比如静态对象等。基于这个特点,JVM将堆内存划分为不同的代:
- 新生代:大多数新创建的对象都会首先分配在新生代。新生代中的对象存活率较低,很多对象在经过一次垃圾回收后就不再存活。
- 老年代:在新生代中经过多次垃圾回收仍然存活的对象会被晋升到老年代。老年代中的对象存活率相对较高,存活时间也较长。
- 永久代(Java 8之前)/元空间(Java 8及以后):在Java 8之前,方法区被称为永久代,用于存储类信息、常量、静态变量等数据。Java 8之后,移除了永久代,引入了元空间,元空间使用本地内存,不再像永久代那样受堆内存大小的限制。
(二)各代采用的垃圾回收算法
- 新生代
- 新生代通常采用复制算法。因为新生代对象存活率低,每次垃圾回收时只有少量对象存活,将存活对象复制到另一块空间的开销相对较小。为了提高内存利用率,新生代又被细分为一个伊甸园区(Eden)和两个幸存者区(Survivor 0和Survivor 1)。一般情况下,新创建的对象会先分配到伊甸园区,当伊甸园区满时,会触发一次Minor GC(新生代垃圾回收),将伊甸园区和其中一个幸存者区中存活的对象复制到另一个幸存者区中,然后清空伊甸园区和原来的幸存者区。
- 老年代
- 老年代对象存活率较高,复制算法的内存利用率低的缺点在这里就不适用了,所以老年代一般采用标记 - 清除算法或者标记 - 整理算法。当老年代内存空间不足时,会触发Major GC(老年代垃圾回收)或Full GC(对整个堆内存进行垃圾回收),对老年代中的对象进行回收。
- 永久代/元空间
- 在Java 8之前,永久代的垃圾回收主要针对废弃的常量和无用的类信息。对于常量,当不再有引用指向它们时会被回收;对于无用的类信息,需要满足该类的所有实例都已被回收、加载该类的ClassLoader已被回收等条件才会被回收。在Java 8及以后的元空间,由于使用本地内存,垃圾回收主要是回收不再使用的类元数据等信息,垃圾回收机制相对更复杂,与堆内存的垃圾回收也有一定的协同工作。
(三)分代收集算法的优势
分代收集算法根据不同代中对象的特点,采用合适的垃圾回收算法,充分利用了对象存活周期的差异,提高了垃圾回收的效率,同时也兼顾了内存的有效利用。例如,在新生代频繁发生的Minor GC中,由于采用复制算法,能快速处理大量新创建且存活时间短的对象,减少了垃圾回收的停顿时间;而在老年代,虽然垃圾回收频率相对较低,但采用标记 - 清除或标记 - 整理算法可以更有效地处理存活时间长的对象,避免内存碎片化等问题。
通过深入理解Java的垃圾回收机制,包括GC的概念、对象可回收的判断时机、不同的垃圾回收算法以及分代收集算法等,Java开发者可以更好地编写高效、稳定的Java程序,也能在遇到与内存相关的性能问题时,更准确地进行分析和调优。