JVM垃圾收集

简介: 本博客为《深入理解java虚拟机》的学习笔记所以大部分内容来自此书。 一 是否可以回收 Java中对象能否被回收取决于对象是“死”还是“活”下面我们就介绍一下判定算法。


本博客为《深入理解java虚拟机》的学习笔记,所以大部分内容来自此书。

 是否可以回收?

Java中对象能否被回收取决于对象是“死”还是“活”,下面我们就介绍一下判定算法。

1  引用计数

1)   算法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器加一;当引用失效时,计数器减一;计数器的值为0时,对象就是不能被使用的了,即处于可以被回收的状态了。

2)   分析

这种算法的优点就是简单,但java中无法使用这种方法,因为java中允许对象循环引用,例如Student、Teacher这种模型,我们可以再在Student中引用Teacher,同时在Teacher中引用Student,此时计数器的值无法恢复到0,即无法被回收,这显然与我们的预期不一致。

 

2  可达性分析

java、C#等主流语言的实现中,均是通过可达性分析来判断对象是否存活的。

1)   算法

通过一系列被称之为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称之为引用链(Reference Chain),当一个对象与GC Roots没有引用链相连时,则证明是不可用的。模型如下:

30dd8bb1504e5a4ea3e311f373eb6004e2ba1155

 

2)   GC Root

Java中可以作为GC Root对象有以下几种:

  虚拟机栈(栈帧中的本地变量表)引用的对象。

  方法区中类的静态属性引用的对象。

  方法去中常量引用的对象。

  本地方法栈中JNI(即一般说的native方法)引用的对象。

 

3)   引用

JDK1.2之后,java支持以下几种引用:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference),他们的强度一次递减。

a)    强引用

只要强引用存在,垃圾收集器就不会回收他们。常用的Object obj = new Object()就属于强引用。

b)    软引用

对于软引用关联的对象,在系统将要发生内存溢出前,将会把这些对象列入回收返回,进行第二次回收。如果这次回收后依然没有足够的内存,才会抛出内存溢出异常。可以通过SoftReference类可以实现软引用。

由此可见,软引用是用来描述还有用但是非必须的对象。

c)    弱引用

使用弱引用关联的对象,只能存活到下一次垃圾收集发生之前,即垃圾收集器工作时,总会收集这部分对象。可以通过WeakReference类来实现弱引用。

由此可见,弱引用也是用来描述还有用但是非必须的对象。

d)    虚引用

虚引用也称幽灵引用或幻影引用,是最弱的一种引用关系。虚引用不会对对象的生命周期产生影响,无法通过虚引用获得一个对象的实例。

对一个对象设置虚引用关联的唯一目的是能够在对象呗垃圾收集器回收时收到一个系统通知。可以通过PhantomReference类来实现虚引用。

 

3  生死判定

通过可达性分析,在引用链上的对象都不能被回收,但是不在引用链上的对象也不是说一定是“非死不可”的。整个判定过程需要经历两次标记:

1)   第一次标记

如果通过可达性分析后,发现一个对象与GC Root不存在相关联的引用链,那么他将被第一次标记,并且进行一次筛选,即是否有必要执行此对象的finalize()方法。以下两种情况被认为是没有必要执行:对象没有覆盖Object的finalize()方法,或者finalize()方法已经被虚拟机执行过。

如果对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列中,并在稍后由一个虚拟机自动创建、低优先级的Finalizer线程去执行对象的finalize()方法。

finalize()方法时对象逃脱死亡的最后一次机会,如果对象希望拯救自己,那么需要将自己与引用链上的任何一个对象关联起来。例如:将this赋值给某个类变量或者某个对象的成员变量。

注意:虚拟机会触发执行finalize()方法,但是并不会保证等待这个方法执行结束;原因是:如果一个对象在finalize方法中执行缓慢或者死循环,可能会导致F-Queue中其他的对象永久的处于等待,甚至导致整个内存回收崩溃。

2)   第二次标记

GC将会对F-Queue中的对象进行第二次标记,如果对象在finalize()方法中拯救了自己,那么此次标记中会将此对象移除“即将回收”的集合;如果对象在finalize()方法没有拯救自己,那么基本上就认为这个对象已经死了,可以被回收了。

 

4  回收方法区

java虚拟机规范中没有要求在方法区实现垃圾回收。在这一部分进行垃圾回收的性价比一般较低。

永久代的垃圾回收主要包括:废弃常量和无用的类。

1)   回收废弃常量

废弃常量的回收和java堆的的回收非常类似。

以字符串为例,如果常量池中的字符串“abcd”已经没有任何对象引用了,即没有其他地方引用这个“字面量”,如果发生内存回收,那么此“abcd”字符串将会被回收。

常量池中的其他类、接口、方法、字段的符号引用也与之类似。

2)   回收无用的类

类只有满足以下条件才能算作是无用的类。

  该类的所有实例已经被回收,即java堆中不存在该类的任何实例。

  加载该类的ClassLoader已经被回收。

  该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射方位该类的方法。

和堆中对象不同的是,虚拟机可以对无用的类进行回收,但并不是不用了就一定会被回收。

 

 如何回收?

常见的垃圾收集算法有:标记-清除算法(Mark-Sweep)、复制算法(Coping)、标记-整理算法(Mark-Compact)。

1  标记-清除算法

1)   算法

标记-清除分为标记、清除两个阶段。

标记出所有需要回收的对象,在标记完成后统一回收被标记的对象。标记的过程就是前面描述的“生死判定”过程。

2)   不足

Ø  效率不高,标记和清除两个过程效率都不高。

Ø  标记-清除后悔产生大量不连续的内存碎片。

3)   模型

8ff1fca1d20e09f50c362fea3010e1a4aece15cf

 

2  复制算法

复制算法主要是解决标记清楚算法效率问题而设计的。

1)   算法

将内存按照容量划分成大小相等的两块每次只使用其中的一块,当一块使用完了,将还存活的对象复制到另外一块上,然后将已经使用过的内存空间一次清理掉。

2)   分析

每次对其中的一个块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只需要移动指针到堆顶,然后按顺序分配内存即可。此种方式实现简单、运行高效。

缺点是内存缩小了一半。

3)   模型

50e39007ad1121d9d905c310357a56f795c40ac2

4)   改进

a)    改进余地

绝大多数数对象都是朝生夕死的,所以按照1:1的比例来划分内存空间是非常浪费的。一般的做法是:将内存划分成一块较大的Eden空间和两块Survivor空间,每次使用Eden和一块Survivor。当回收时将存活的对象一次性的复制到另外一块Survivor中,然后一次性的清理掉Eden和刚才使用的Survivor空间。

Hotspot虚拟机默认的Eden : Survivor = 8 : 1,即每次使用整个内存空间的90%,浪费10%的空间。

b)    改进模型

edcce2b92667d9b0ef2a7c2a06a63c0c669cc1e0

 

c)    新的问题

如果某次gc后发现存活对象的内存大于survivor的大小,将会需要老年代及进行担保。

如果每次gc后存活的对象较多,频繁的复制到另一个survivor中也会导致性能降低。

3  标记-整理算法

复制算法的两个问题,导致了其不适合作为老年代的回收算法。

1)       算法

和“标记-清除”算法一样,标记-整理算法也是通过上述“生死判定”过程对对象进行标记,接着将所有存活的对象向一端移动,然后清理掉边界之外的内存。

2)       模型

ce544bc994680b7feb525c0ddf53a909031cac6d

 

4  分代收集算法

根据对象存活周期的不同,将内存划分为几块,一般将java堆分为新生代、年老代,并根据各个年代的特点采用合适的收集算法。

新生代中,绝大多数对象都是迅速死去,只有少量存活,适合采用优化后的复制算法,只需要付出少量的复制成本就可以完成收集。

年老代因为对象存活率高,并且没有额外的空间对它进行分配担保,所以就必须采用标记-清除、标记-整理算法进行回收。

 

 

相关文章
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
55 2
|
4月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
4月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
31 3
|
4月前
|
C# UED 开发者
WPF打印功能实现秘籍:从页面到纸张,带你玩转WPF打印技术大揭秘!
【8月更文挑战第31天】在WPF应用开发中,打印功能至关重要,不仅能提升用户体验,还增强了应用的实用性。本文介绍WPF打印的基础概念与实现方法,涵盖页面元素打印、打印机设置及打印预览。通过具体案例,展示了如何利用`PrintDialog`和`PrintDocument`控件添加打印支持,并使用`PrinterSettings`类进行配置,最后通过`PrintPreviewWindow`实现打印预览功能。
425 0
|
4月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
205 0
|
4月前
|
算法 Java 程序员
【JVM的秘密花园】揭秘垃圾收集器的神秘面纱!
【8月更文挑战第25天】在Java虚拟机(JVM)中,垃圾收集(GC)自动管理内存,回收未使用的对象以避免内存泄漏和性能下降。本文深入介绍了JVM中的GC算法,包括串行、并行、CMS及G1等类型及其工作原理。选择合适的GC策略至关重要:小型应用适合串行收集器;大型应用或多核CPU环境推荐并行收集器或CMS;需减少停顿时间时,CMS是好选择;G1适用于大堆且对停顿时间敏感的应用。理解这些能帮助开发者优化程序性能和稳定性。
37 0
|
4月前
|
算法 Java
JVM自动内存管理之垃圾收集器
这篇文章是关于Java虚拟机(JVM)自动内存管理中的垃圾收集器的详细介绍。
|
5月前
|
监控 算法 Java
深入理解Java虚拟机:垃圾收集机制的演变与最佳实践
【7月更文挑战第14天】本文将带领读者穿梭于JVM的心脏——垃圾收集器,探索其设计哲学、实现原理和性能调优。我们将从早期简单的收集算法出发,逐步深入到现代高效的垃圾收集策略,并分享一些实用的调优技巧,帮助开发者在编写和维护Java应用时做出明智的决策。
57 3
|
5月前
|
算法 Java
Java面试题:列举并解释JVM中常见的垃圾收集器,并比较它们的优缺点
Java面试题:列举并解释JVM中常见的垃圾收集器,并比较它们的优缺点
106 3
|
5月前
|
Java UED
Java面试题:描述JVM中垃圾收集的Stop-The-World现象及其影响
Java面试题:描述JVM中垃圾收集的Stop-The-World现象及其影响
58 1