JVM之垃圾回收

垃圾回收算法

垃圾回收(Garbage Collection,GC)是计算机程序在运行时自动回收不再被使用的内存的过程。这是为了防止因长时间运行程序而导致的内存泄露。Java 的 JVM、Python 解释器、.NET 平台等都使用了垃圾回收机制。

  1. 标记-清除算法(Mark-Sweep):

    • 标记阶段:遍历所有可访问到的对象,并将其标记为“活动”。
    • 清除阶段:再次遍历堆,对于未标记的对象(即不再被引用的对象),释放其占用的内存。
    • 问题:这种方法可能导致内存碎片化。
  2. 标记-复制算法(Mark-Copy):

    • 与“标记-清除”类似,首先进行标记阶段。
    • 不是简单地清除未标记的对象,而是将所有标记的对象复制到另一个区域,并连续排列它们。
    • 之后,原始区域的全部内容都可以被看做是垃圾并整体被回收。
    • 优点:解决了内存碎片化的问题。
    • 缺点:需要两倍的空间来存储对象,并且复制对象会增加一些开销。
  3. 标记-整理算法(Mark-Compact):

    • 标记阶段与前两者相同。
    • 在整理阶段,活动对象被连续地移动到堆的开始位置,而非复制到另一个区域。
    • 此方法试图结合“标记-清除”和“标记-复制”两者的优点:它解决了碎片化的问题,同时不需要额外的内存空间。
    • 缺点:对象移动到堆的开始位置会增加一些开销。

以上所述的每种策略都有其优点和缺点。在实际的垃圾回收实现中,经常会结合多种策略来获得最佳的性能和效率。例如,Java 的 HotSpot JVM 使用的是一种组合了复制和标记-清除的方法,其中新生代使用复制算法,而老年代使用标记-清除-整理算法。

如何判断一个对象是否死亡

  1. 引用计数法
    1. 对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾
    2. 弊端:如果AB相互持有引用(循环引用),导致永远不能被回收。堆内存中实例堆积。生产中:本地方法栈指向堆内存产生内存泄漏。
  2. 可达性分析(根搜索算法)
    1. 通过GC Root的对象,开始向下寻找,看某个对象是否可达
    2. 能作为GC Root:类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等。GC Roots本质上一组活跃的引用
    1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    2. 方法区中类静态属性引用的对象。
    3. 方法区中常量引用的对象。
    4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。
    3. 这些对象被作为 GC-Roots 的原因是,它们都有直接或间接地与其他对象产生引用关系,它们的存在可以保证一部分对象的可达性。通过从 GC-Roots 开始的引用链追踪,垃圾回收器可以确定哪些对象是可达的,从而判断哪些对象是不可达的,进而回收不可达对象的内存

几种常见垃圾回收器

cms

CMS(Concurrent Mark-Sweep)垃圾回收器是Java HotSpot虚拟机中的一种垃圾回收器,主要用于老年代(Tenured Generation)的回收。它是一种以获取最短回收停顿时间为目标的回收器,特别适合对响应时间有较高要求的应用。

CMS什么时候会STW?为什么要STW?

CMS(Concurrent Mark-Sweep)垃圾回收器的回收流程主要分为以下几个阶段:

初始标记(Initial Mark):此阶段的目的是为了标记所有的根对象,也就是直接与GC Roots关联的对象。这一步需要"Stop-The-World",但其停顿时间一般较短。

并发标记(Concurrent Mark):在此阶段,垃圾回收线程与应用线程并发执行,遍历对象图并标记所有存活的对象。

预清理(Precleaning):此阶段仍与应用线程并发执行,修正并发标记阶段因并发修改而导致的不准确情况。这一步的目的是为了减少下一步重新标记的工作量。

重新标记(Remark):此阶段为了确保标记的准确性,需要"Stop-The-World"。在此阶段,垃圾回收线程会修正并发标记期间因为对象引用变化而造成的标记误差。

并发清除(Concurrent Sweep):标记完存活的对象后,与应用线程并发地进行非存活对象的清除。

并发重置(Concurrent Reset):重置CMS算法相关的内部状态,为下一次垃圾收集循环做准备。

在CMS的回收流程中,主要有两次STW(“Stop-The-World”)时间,分别是“初始标记”和“重新标记”阶段。

为什么要stw?

STW(Stop-The-World)是垃圾收集过程中的一个阶段,此时所有的应用线程都会被暂停,只有垃圾收集线程会运行。那么,为什么需要STW呢?

数据一致性和准确性:垃圾回收的主要任务是回收不再被引用的对象。为了准确地标记这些对象,垃圾收集器需要确保在检查对象引用的时候,应用线程不会更改这些引用。如果在垃圾回收过程中,应用线程仍然运行并更改引用,那么垃圾收集器可能会遗漏某些应该被回收的对象,或者错误地回收仍被引用的对象。

简化垃圾回收算法:暂停所有的应用线程可以简化垃圾回收算法,因为不需要处理并发问题。这可以加速垃圾回收过程,并降低实现复杂性。

对象的整理和压缩:在某些垃圾回收策略中,如复制算法或压缩算法,需要移动对象以整理内存。这时,如果应用线程仍在运行,可能会访问到被移动的对象,导致错误。STW可以确保在对象移动期间,应用线程不会访问这些对象。

安全点(Safepoint)机制:在JVM中,STW的实现通常是基于安全点机制的。当JVM决定要执行STW时,它会让所有的应用线程在下一个安全点停下来。在安全点,JVM可以确保线程的执行状态是已知的,从而在这种状态下执行垃圾回收。

虽然STW可以确保垃圾回收的准确性和简化算法,但也带来了应用暂停的开销,这可能会影响应用的响应时间和吞吐量。因此,近年来,许多工作都集中在减少STW的时间或尝试实现低暂停时间的垃圾收集策略上。例如,G1、Shenandoah和ZGC等垃圾回收器就是为了减少STW而设计的。

G1

使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。每个Region大小都是一样的,可以是1M到32M之间的数值,但是必须保证是2的n次幂

所谓Garbage-Frist,其实就是优先回收垃圾最多的Region区域

(1)分代收集(仍然保留了分代的概念)
(2)空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
(3)可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)

工作过程可以分为如下几步

初始标记(Initial Marking) 标记以下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
并发标记(Concurrent Marking) 从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
最终标记(Final Marking) 修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
筛选回收(Live Data Counting and Evacuation) 对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划

垃圾回收中的三色标记法

三色标记法是垃圾回收中用来追踪对象可达性的一种技术。在使用三色标记法的垃圾回收算法中,对象根据其可达性状态被分为三类,并用三种颜色来标记:白色、灰色和黑色。

  1. 白色:这些对象是未经访问的,意味着垃圾回收器尚未确定这些对象是否还是可达的。在算法开始时,所有对象都是白色。

  2. 灰色:这些对象是已经被访问(或者说已经被标记)但其所引用的对象尚未被完全访问的对象。换句话说,一个灰色对象可能有一些白色的引用对象。

  3. 黑色:这些对象不仅已经被访问,而且它们引用的所有对象也都被访问过了。这意味着黑色对象不会引用任何白色对象。

在标记阶段:

  • 开始时,从根出发,所有对象都被视为白色。
  • 当一个对象被访问(或标记)时,它会变成灰色。
  • 当一个灰色对象的所有引用都被访问时,它变成黑色。
  • 这个过程会继续,直到没有灰色对象为止,这时所有可达对象都会是黑色的,而不可达对象仍然是白色的。

三色不变性:

在标记过程中,系统保持以下不变性,这是为了确保算法的正确性:

  1. 黑色对象不会直接引用白色对象:这确保了我们不会错过任何可达对象。
  2. 只有灰色对象可以被标记为黑色:这确保了我们完整地标记了所有可达对象。

使用三色标记法的好处是,它可以很容易地与其他技术(如并发标记)结合使用。例如,在并发标记的情境下,应用线程和垃圾回收线程可以同时运行,只要维持三色不变性即可。

yong GC 的发生时间?发生频繁,发生时间长的原因和解决方案?

“Young GC”,即 Java 垃圾收集器中新生代的垃圾收集。新生代是堆内存中的一部分,其中包含了新创建的对象。当这些对象变得不可达时,它们被视为垃圾并由 Young GC 进行回收。

Young GC 的发生时间:

当新生代区域满了时,就会触发 Young GC。
新生代被分为 Eden 区和两个 Survivor 区(S0 和 S1)。当 Eden 区满时,就会触发 Young GC。

频繁发生 Young GC 的原因:

堆内存分配不足。
新生代的大小设置得太小。
应用程序创建大量短暂的临时对象。

Young GC 时间过长的原因:

新生代中有大量对象需要处理。
Survivor 空间不足,导致大量对象提前晋升到老年代。
垃圾收集器的选择不当或配置不当。

解决方案:

增加堆内存:通过调整 -Xms 和 -Xmx 参数来增加 JVM 的起始和最大堆内存。
调整新生代大小:使用 -Xmn 参数来设定新生代的大小。确保它足够大,以减少 Young GC 的频率,但又不要太大以至于影响老年代的大小。
优化对象创建和使用:减少创建大量临时对象,重用对象等。
选择合适的垃圾收集器:例如,对于需要低延迟的应用,可以考虑使用 G1 或 ZGC。
调整 Survivor 空间的大小:可以使用 -XX:SurvivorRatio 参数来调整 Eden 和 Survivor 区的大小比例。
监控和调优:使用工具如 VisualVM、JConsole 或其他商业工具来监控垃圾收集活动,并根据监控数据进行适当的调优。

什么情况下老年代会发生GC?

老年代(Old Generation 或 Tenured Generation)主要存储应用程序中的长寿命对象。在一定的条件下,老年代会触发垃圾收集,通常这种垃圾收集称为 Major GC 或 Full GC。以下是老年代可能会发生垃圾收集的情况:

  1. 空间不足:最常见的触发老年代 GC 的原因是老年代空间不足。当新生代中的对象在经过一定次数的 Young GC 后仍然存活,它们就会被晋升到老年代。如果老年代空间不足以容纳这些被晋升的对象,就会触发 Full GC。

  2. 显式调用:可以通过代码或者工具显式调用 System.gc() 来触发 Full GC。但这并不是一种推荐的做法,因为它会导致应用程序的明显停顿。

  3. Metaspace 或 PermGen 空间不足:在 Java 8 之前,持久代(PermGen)存储了 Java 类的元数据,而在 Java 8 及其之后,这些数据存储在 Metaspace。如果这些空间不足,也可能会触发 Full GC。

  4. CMS GC 的特定阶段:在使用并发标记-扫描(CMS)收集器时,尽管它的许多阶段都是并发的,但在某些情况下,如初始化标记和重新标记阶段,可能会触发短暂的 STW(Stop-The-World)事件。

  5. 垃圾收集器的策略:某些垃圾收集器,如 Parallel GC,经常在进行 Young GC 时同时执行 Full GC。

为了尽量减少老年代的 GC 频率和持续时间,可以通过增加堆内存、调整晋升阈值、选择合适的垃圾收集器和其他 JVM 调优措施来实现。不过,老年代的垃圾收集通常涉及整个堆,所以 Full GC 的停顿时间通常比 Young GC 要长。

垃圾回收会发生在哪几个区域?

在 Java 虚拟机(JVM)中,垃圾回收主要发生在以下几个区域:

  1. 新生代 (Young Generation):新生代又可分为以下三个子区域:

    • Eden 区:大部分新创建的对象首先被分配到 Eden 区。
    • Survivor 区:这部分又分为 S0 和 S1。当 Eden 区进行垃圾收集后,仍然存活的对象会被移动到一个 Survivor 区(比如 S0),然后在后续的收集过程中,这些对象可能会在两个 Survivor 区之间移动,或者在达到一定年龄后被晋升到老年代。

    当新生代中的对象被清除或移动时,我们通常称之为 Minor GC。

  2. 老年代 (Old or Tenured Generation):经过多次 Minor GC 后仍然存活的对象会被晋升到老年代。当老年代进行垃圾收集时,我们通常称之为 Major GC 或 Full GC。

  3. Metaspace (Java 8 及之后) / 永久代 (PermGen, Java 8 之前)

    • Metaspace:用于存放 JVM 加载的类信息、常量、静态变量等元数据。当 Metaspace 空间不足时,会触发 Full GC。
    • 永久代 (PermGen):在 Java 8 之前的版本中,JVM 使用了名为永久代的区域来存储类的元数据。从 Java 8 开始,PermGen 被 Metaspace 所替代。

垃圾回收主要是针对新生代和老年代进行的,其中新生代的回收频率通常较高,因为应用程序经常创建新的对象。老年代的垃圾收集频率较低,但每次收集的时间通常较长。而对于 Metaspace 或 PermGen 的垃圾收集,通常是伴随 Major GC 或 Full GC 一起进行的。

说一下垃圾回收?如果GC突然很慢怎么排查,比如原来GC完成只需要1秒,现在要5秒?了解哪些gc相关的工具,比如jstack之类的

垃圾回收(Garbage Collection, GC)是自动内存管理的过程。其主要目的是自动跟踪那些不再被程序使用的内存并释放它们,以避免内存泄露和应用程序中可用内存的耗尽。

垃圾回收的基本过程

  1. 标记 (Mark):在此阶段,GC 会标记出所有存活的对象。
  2. 清除 (Sweep)/复制 (Copy)/整理 (Compact):在此阶段,GC 会删除未标记的对象并且可能会重新整理内存,使得存活的对象排列紧凑。

若GC突然变慢,可以采取以下策略来排查

  1. 查看GC日志:通过启用GC日志 (-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:<path-to-log>),可以获取关于GC活动的详细信息。
  2. 内存泄露:内存泄漏可能导致频繁的Full GC。使用工具如VisualVM、MAT (Memory Analyzer Tool) 可以帮助检测内存泄露。
  3. 配置的内存大小:检查是否近期有调整JVM的堆大小,可能是配置了一个较小的堆空间。
  4. 检查代码的变动:最近的代码更改可能导致更多的对象创建,导致更频繁的GC或长时间的GC。
  5. 外部系统影响:例如,如果你的应用依赖于远程服务,这些服务的延迟可能导致你的应用中对象存活时间的增长。

GC相关的工具

  1. jstat:实时查看JVM统计信息,用于监控JVM性能。
  2. jstack:用于打印出Java线程的栈跟踪,可以帮助诊断死锁或其他线程相关问题。
  3. VisualVM:这是一个强大的工具,可以监控、分析、配置及故障排查Java应用。
  4. JConsole:用于监视Java应用程序。
  5. MAT (Memory Analyzer Tool):专门用于分析heap dump,帮助找出内存泄露。
  6. GCViewer:用于可视化GC日志。

当面对GC性能问题时,首先建议查看GC日志以获得初步了解,然后结合其他工具进行深入分析。

假设HashMap里面存放100万个对象,那么gc可能会有什么问题?

HashMap中存放了100万个对象,可能会面临以下与GC相关的问题:

  1. 大的对象引用:如果HashMap的大小迅速增长到100万,并且大部分的对象都是在短时间内创建的,那么这些对象会集中地从Young Generation晋升到Old Generation(老年代)。这可能导致老年代的空间被迅速使用完,从而引起频繁的Full GC。

  2. Full GC时间增加:当大量对象从Young Generation晋升到Old Generation,Full GC的时间可能会显著增加,因为它需要清理老年代中的死亡对象。Full GC通常比Minor GC更耗时,因为它涉及到整个堆的回收,包括老年代、Young Generation和PermGen/Metaspace。

  3. 内存泄露:如果这些对象长时间保留在HashMap中且不被使用,那么它们可能成为内存泄漏的来源。随着时间的推移,这会导致可用内存减少,从而引发更频繁的GC活动。

  4. 内存溢出:如果没有足够的内存来容纳100万个对象,您可能会遇到OutOfMemoryError

  5. GC效率下降:高频率的GC可能会导致应用程序的吞吐量下降,因为GC过程中可能需要停止应用线程(Stop-The-World事件)。

为了解决上述问题,可以考虑以下策略:

  1. 调整JVM参数:增加堆大小或调整Young/Old Generation的比例来适应当前的工作负载。
  2. 使用弱引用键 (WeakHashMap):当键不再被使用时,允许GC回收其条目,从而避免内存泄漏。
  3. 代码优化:定期清除HashMap中不再需要的条目。
  4. 监控工具:使用监控工具(如JVisualVM、JConsole、MAT等)来跟踪和优化内存使用和GC性能。

最后,虽然存储100万个对象在许多现代应用中是完全可行的,但始终要确保您的系统和代码已经为此种规模的数据量进行了适当的优化和调整。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值