java的内存分配和回收机制

Java 与 C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。

  1. 概述

垃圾收集(GC)需要完成的三件事情:

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

Java 内存运行时区域中程序计数器****、虚拟机栈、本地方法栈 3 个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着出 栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,在这几个区域 内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着 回收了。

Java 堆和方法区这两个区域则有着很显著的不确定性, 一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样, 只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分 内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理,本文后 续讨论中的“内存”分配与回收也仅仅特指这一部分内存。

这里方法区和虚拟机栈垃圾回收处理策略不同在于,虚拟机栈中方法执行完毕会自己出栈,而方法区中的类信息需要垃圾回收机制来回收,二者生命周期不同,虚拟机栈在线程结束是就会被销毁,不需要额外的垃圾回收,方法区在JVM创建时创建,关闭时销毁

  1. 对象已死

在堆里面存放着 Java 世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前, 第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”,下面是几种判断对象是否存活的算法

  1. 引用计数算法

在对象中添加一个引用计数器,每 当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的

引用计数算法(Reference Counting)虽然占用了一些额外的内存空间来 进行计数,但它的原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。但是,在 Java 领域,至少主流的 Java 虚拟机里面都没有选用 引用计数算法来管理内存,这个看似简单的算法有很多例外情况要考虑, 必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。

  1. 可达性分析算法

当前主流的商用程序语言的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。

这个算法的基本思路就是**通过一系列称为“**GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到 GC Roots 间没有任何引用链相连,或者用图论的话来说就是 从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的。

在这里插入图片描述

在 Java 技术体系里面,固定可作为 GC Roots 的对象包括以下几种:

  • 虚拟机****栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法 堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象,譬如 Java 类的引用类型静态变量。 ·在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  • 本地方法栈中 JNI(即通常所说的 Native 方法)引用的对象
  • Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象 (比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  • 所有被同步锁(synchronized 关键字)持有的对象
  • 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。

除了这些固定对象,根据用户的选择不同的垃圾回收器以及当前回收的内存区域,还会有临时性其他对象的加入,构成完整的GC Roots对象集合

譬如后文将会提到的分代收集和局部回收,如果只针对 Java 堆中某一块区域发起垃圾收集(如最典型的只针对新生代的垃圾收集),必须考虑到内存区域是虚拟机****自己的实现细节(在用户视角里任何内存区域都是不可见的),更不是孤立封闭的,所以某个区域里的对象完全有可能被位于堆中其他区域的对象所引用,这时候就需要将这些关联区域的对象也一并加入 GC Roots 集合中去,才能保证可达性分析的正确性。

  1. 再谈引用

上面的两种判断算法,都需要通过引用判断对象是否存活,引用技术算法需要判断对象的引用数量,可达性算法需要判断对象的引用链是否可达,这里就需要具体处理引用的定义,在JDK 1.2 版之前,Java 里面的引用是很传统的定义:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该 reference 数据是代表某块内存、某个对象的引用。

这种情况下,一个对象只有被引用和不被引用俩种状态,对一些对象的描述就有些乏力,比如一些对象我们需要在内存空间充足的情况下保留,内存紧张的时候回收,很多系统的缓存功能都符合这样的应用场景。

JDK 1.2 版之后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用和****虚引用4 种,这 4 种引用强度依次逐渐减弱。

  • 强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类 似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在, 垃圾收集器****就永远不会回收掉被引用的对象。
  • 软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在 系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果 这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 版之后提供了 SoftReference 类来实现软引用。
  • 弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,**无论当前内存是否足够,都会回收掉只被弱引用关联的对象。**在 JDK 1.2 版之后提供了 WeakReference 类来实现弱引用。
  • 虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时 收到一个系统通知。在 JDK 1.2 版之后提供了 PhantomReference 类来实现虚引用。
  1. 生存还是死亡?

finalize**()能做的所有工作,使用 try-finally 或者其他方式都可以做得更 好、更及时,所以笔者建议大家完全可以忘掉 Java 语言里面的这个方法。**

在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程

如果对 象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记,

随后进行一次筛选,筛选的条件是此对象是否有必要执行 finalize**()方法**。假如对象没有覆盖 finalize()方法,或者 finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。

如果这个对象被判定为确有必要执行 finalize()方法,那么该对象将会被放置在一个 名为 F-Queue 的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的 Finalizer 线程去执行它们的 finalize() 方法。(执行是会开始,但是不会等他结束)。finalize()方法是对象逃 脱死亡命运的最后一次机会,稍后收集器将对 F-Queue 中的对象进行第二次小规模的标 记,如果对象要在 finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建 立关联即可

任何一个对象的 finalize()方法都只会被系统自动 调用一次,如果对象面临下一次回收,它的 finalize()方法不会被再次执行

  1. 回收方法区

有人认为方法区是没有垃圾收集行为的,《Java 虚拟机规范》中提到过可以不要求虚拟机在方法区中实现垃圾收集

也确实有虚拟机没有回收方法区,方法区垃圾收集的“性价比”通常也是比较低的

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型

  1. 垃圾收集算法

垃圾收集算法可以划分为**“引用计数式垃圾收集” 和“追踪式垃圾收集”两大类,这两类也常被 称作“直接垃圾收集”和“间接垃圾收集”,**下面介绍的算法都属于追踪式垃圾收集

  1. 分代收集理论

分代收集名为理论,实质是一套符合大多数程序运行实 际情况的经验法则,它建立在两个分代假说之上:

  • 弱分代假说:绝大多数对象都是朝生夕灭的。
  • 强分代假说:熬过越多次垃圾收集过程的对象就 越难以消亡。

俩个假说确定了垃圾收集器的一致设计原则:收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。

在 Java 堆划分出不同的区域之后,垃圾收集器才可以每次只回收其中某一个或者某些部分的区域,也才能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法——因而发展出了“标记-复制算法”“标记-清除算法”“标记-整理算法”等针对性的垃圾收集算法。

分代收集理论具体放到现在Java 虚拟机里,设计者一般至少会把 Java 堆 划分为新生代和老年代两个区域,顾名思义, 在新生代中,每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象, 将会逐步晋升到老年代中存放。

分代收集并非只是简单划分 一下内存区域那么容易,它至少存在一个明显的困难:**对象不是孤立的,对象之间会存 跨代引用。这种情况下回收新生代中的对象,判断存活不光需要判断它的GC roots集合,还需要判断老年代中的数据,**为了解决这个问题,就需要对分代收集理论添加第三条经验法则:

  • 跨代引用假说:跨代引用相对于同代引 用来说仅占极少数。

这其实是可根据前两条假说逻辑推理得出的隐含推论:存在互相引用关系的两个对 象,是应该倾向于同时生存或者同时消亡的。

我们就不应再为了少量的跨代引用去扫描整个老年代,只需在新生代上建立一个全局的数据结构(该结构被称为“记忆集”,Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生 Minor GC 时,只有 包含了跨代引用的小块内存里的对象才会被加入到 GCRoots 进行扫描。虽然这种方法需 要在对象改变引用关系(如将自己或者某个属性赋值)时维护记录数据的正确性,会增 加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。

  1. 标记—清除算法

最早出现也是最基础的垃圾收集算法是**“标记-清除”算法,这个算法有俩个阶段,标记和清除,**首先标记出所有的需要的回收的对象,标记完成后同一回收所有被标记的对象,或者反过来标记存活的对象,然后回收未被标记的对象,**这个标记过程就是对垃圾的判断过程,**就是前面对象已死中提到的内容

后面的算法都是在这个基础上针对缺点进行了更改,标记清除算法主要有俩个缺点,

  • 执行效率不稳定:如果由java堆中有大量对象需要进行回收,这时候就需要大量进行标记和清除的动作,俩个动作的执行效率都会随对象的增长而降低,
  • **内存空间碎片化:**标记清除会产生大量碎片化的内存碎片,然后碎片太多可能导致在后续内存分配的时候一些比较大的对象分配时无法找到足够的连续内存
  1. 标记-复制算法

为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,标记复制算法应运而生,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。**当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。**如果内存中多数对象都是存活的,这种算法将会 产生大量的内存间复制的开销,但对于多数对象都是可回收的情况,算法需要复制的就是占少数的存活对象,而且每次都是针对整个半区进行内存回收,分配内存时也就不用考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。

Andrew Appel 针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策略,现在称为“Appel 式回收”Appel 式回收的具体做法是把新生代分为一块较大的 Eden 空间和两块较小的 Survivor 空间,**每次分配内存只使用 Eden 和其中一块 Survivor。发生垃圾搜集时,将 Eden 和 Survivor 中仍然存活的对象一 次性复制到 另外一块 Survivor 空间上,然后直接清理掉 Eden 和已用过的那块 Survivor 空间。**HotSpot 虚拟机默认 Eden 和 Survivor 的大小比例是 8∶1,也即每次新生代中可用内存空间为整个新生代容量的 90%(Eden 的 80%加上一个 Survivor 的 10%),只有一 个 Survivor 空间,即 10%的新生代是会被“浪费”的。当然,98%的对象可被回收仅仅是 “普通场景”下测得的数据,任何人都没有办法百分百保证每次回收都只有不多于 10%的 对象存活,因此 Appel 式回收还有一个充当罕见情况的“逃生门”的安全设计,当 Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,就需要依赖其他内存区域 (实际上大多就是老年代)进行分配担保(Handle Promotion)。

  1. 标记-整理算法

标记-复制算法在对象存活率较高时就要进行较多的复制操作,效率将会降低。更 关键的是,如果不想浪费 50%的空间,就需要有额外的空间进行分配担保,以应对被使 用的内存中所有对象都 100%存活的极端情况,所以在老年代一般不能直接选用这种算 法。

标记-整理算法,其中的标记过程仍然与“标记-清除”算法一样,但 后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移 动,然后直接清理掉边界以外的内存

工作原理

标记-整理算法也分为两个主要阶段:标记阶段和整理阶段。

标记阶段:

  • 从GC Root集合开始,遍历对象引用图,标记所有可达的对象。
  • 这一步与标记-清除算法中的标记阶段相同,标记过程是递归的,沿着对象引用链进行,直到所有可达的对象都被标记。

整理阶段:

  • 遍历整个堆,将所有存活的对象向一端移动(通常是堆的起始位置),保持对象之间的紧密排列。
  • 更新所有对象的引用,以反映它们的新位置。
  • 移动完成后,释放未被标记对象的内存,未被标记的对象被回收,形成一块连续的空闲区域。

在这里插入图片描述

优点

  • 无内存碎片:对象被紧密排列在一起,没有内存碎片,提高了内存利用率。
  • 高效的内存分配:由于所有存活对象被移动到堆的一端,剩下的内存是连续的,内存分配速度更快。
  • 适用于长生命周期对象:尤其适合老年代(Old Generation)的垃圾回收,因为老年代对象生命周期较长,不需要频繁移动。
  1. HotSpot 的算法细节实现

  2. 根节点枚举

(为了提供元素回收时需要的快照)

这里首先是可达性算法中的****GC Roots引用链处理,java内存中的数据是非常多的,哪怕的方法区中的类或者常量数量都是多到以亿无法计量,

迄今为止,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的,现在可达性分析算法耗时最长的查找引用链的过程已经可以做到与用户线程一起并发,但根节点枚举始终还是必须在一个能保障一致性的快照中才得以 进行——这里“一致性”的意思是整个枚举期间执行子系统看起来就像被冻结在某个时间点上,枚举根节点时也是必须要停顿的。

目前主流 Java 虚拟机使用的都是准确式垃圾收集,所以当用户线程停顿下来之后,其实并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得到哪些地方存放着对象引用的。

在 HotSpot 的解决方案里,是使用一组称为 OopMap 的数据结构来达到这个目的。一旦类加载动作完成的时候, HotSpot 就会把对象内什么偏移量上是什么类型的数据计算出来,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样收集器在扫描时就可以直接得知这些信息了,并不需要真正一个不漏地从方法区等 GC Roots 开始查找。

  1. 安全点

(前面提到过,在根节点的时候需要停下所有线程,这里是为了解决如何停顿用户线程)

在 OopMap 的协助下,HotSpot 可以快速准确地完成 GC Roots 枚举,但是在这种情况下,引用关系发生变化,即改变OopMap集合的操作特别多,每一个指令都会生成对应的OopMap,会额外占据大量额外空间,实际上HotSpot没有为每一个指令生成OopMap,前面提到了在特定位置记录信息,这些位置称为安全点****,安全点是一种特殊的执行点,在这个点上,垃圾回收器可以安全地中断程序执行,开始进行垃圾回收操作。

安全点是程序执行过程中的特定位置,这些位置被选定是因为在这个点上,所有的线程都处于稳定状态,即它们不会引用新的对象或修改现有的对象引用。

有了安全点的设定, 也就决定了用户程序执行时并非在代码指令流的任意位置都能够停顿下来开始垃圾收 集,而是强制要求必须执行到达安全点后才能够暂停。因此,安全点的选定既不能太少 以至于让收集器等待时间过长,也不能太过频繁以至于过分增大运行时的内存负荷。

对于安全点,另外一个需要考虑的问题是,如何在垃圾收集****发生时让所有线程(这 里其实不包括执行 JNI 调用的线程)都跑到最近的安全点,然后停顿下来。这里有两种 方案可供选择:抢先式中断和主动式中断

  • 抢先式中断:在垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地方不在安全点上,就恢复这条线程执行,让它一会再重新中断,直到跑到安全点上。(几乎没有虚拟机使用这种方式)
  • 主动式中断:当垃圾收集需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志位,各个线程执行过程时会不停地主动去轮询这个标志,一旦发现 中断标志为真时就自己在最近的安全点上主动中断挂起。轮询标志的地方和安全点是重合的
  1. 安全区域

(这里是在非执行状态的用户线程停顿问题)

使用安全点的设计似乎已经完美解决如何停顿用户线程,让虚拟机进入垃圾回收状态的问题了

程序在执行的时候可以遇到安全点进行垃圾回收,在程序不执行(没有分配处理器时间)的时候,要如何处理,也就是线程处于Sleep或者Blocked状态,这种情况下线程无法相应虚拟机的中断需求,也无法到达安全点进行中断自己,这种情况需要安全区域来处理,

安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在 这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸 了的安全点。

当线程运行到安全区域时,会标示自己进入了安全区域,在虚拟机进行垃圾回收的时候就不用额外关注处于安全区域的线程,当线程离开安全区域的时候,它会检查虚拟机是否完成了对Roots的枚举,如果完成就正常进行执行,如果没有就等待,等待到它可以离开为止

  1. 记忆集与卡表

(直接扫描整个Roots集合太费时,这里通过记忆集来缩减扫描范围)

讲解分代收集理论的时候,提到了为解决对象跨代引用所带来的问题,垃圾收集器在新生代中建立了名为记忆集的数据结构,为了比避免扫描整个老年代,其实不止在新生代和老年代之间会有这种跨代问题出现,在涉及部分区域收集的时候也会出现这种问题,

记忆集是一种记录从非收集区指向收集区的指针集合的抽象数据结构,

在垃圾收集的场景中,收集器只需要通过记忆集判断出某一块非收集区域是否存在有指向了收集区域的指针就可以了,并不需要了解这些跨代指针的全部细节。那设计者在实现记忆集的时候,便可以选择更为粗犷的记录粒度来节省记忆集的存储和维护成本,下面列举了一些可供选择(当然也可以选择这个范围以外的)的记录精度:

  • 字长精度:每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的 32 位或 64 位,这个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
  • 对象精度:每个记录精确到一个对象,该对象里有字段含有跨代指针。
  • 卡精度:每个记录精确到一块内存区域,该区域内有对象含有跨代指针。

卡表就是记忆集的一种具体实现, 它定义了记忆集的记录精度、与堆内存的映射关系等。

卡表最简单的形式可以只是一个字节数组,而 HotSpot 虚拟机确实也是这样做的。

字节数组的每一个元素对应的内存区域都有一块特定大小的内存块,这个内存块叫做卡页,

在这里插入图片描述

一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字 段存在着跨代指针,那就将对应卡表的数组元素的值标识为 1,称为这个元素变脏 (Dirty),没有则标识为 0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能 轻易得出哪些卡页内存块中包含跨代指针,把它们加入 GC Roots 中一并扫描。

  1. 写屏障

卡表元素是需要维护的,例如它们何时变脏、谁来把它们变脏等。

卡表元素何时变脏的答案是很明确的——有其他分代区域中对象引用了本区域对象 时,其对应的卡表元素就应该变脏,这里的问题是如何使表中元素变脏,在解释字节的时候有足够的介入空间,但是在编译的时候,字节码已经变成了机器指令流,这时候一个如何维护这个表,

在 HotSpot 虚拟机里是通过写屏障技术维护卡表状态的。

写屏障可以看作在虚拟机层面对 “引用类型字段赋值”这个动作的 AOP 切面,在引用对象赋值时会产生一个环形 通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范畴 内。在赋值前的部分的写屏障叫作写前屏障,在赋值后的则叫作写 后屏障

应用写屏障后,虚拟机就会为所有赋值操作生成相应的指令,一旦收集器在写屏障 中增加了更新卡表操作,无论更新的是不是老年代对新生代对象的引用,每次只要对引用进行更新,就会产生额外的开销,不过这个开销与 Minor GC 时扫描整个老年代的代 价相比还是低得多的。

除了写屏障的开销外,卡表在高并发场景下还面临着“伪共享”问题,除了写屏障的开销外,卡表在高并发场景下还面临着“伪共享”为了避免伪共享问题,一种简单的解决方案是不采用无条件的写屏障,而是先检查卡表标记,只有当该卡表元素未被标记过时才 将其标记为变脏

  1. 并发的可达性分析

在可达性分析中,要求比喻冻结全部的用户线程,这里的GCRoots数量还是比较小的,他在优化技巧下,带来的停顿还是比较小的,但是他继续向下遍历对象图,对象数量可能会很多,这种情况下停顿时间就会更长,现在需要想办法降低这部分停顿

为了能解释清楚这个问题,我们引入三色标记作为工具来辅助推导,把遍历对象图过程中遇到的对象,按照“是否访 问过”这个条件标记成以下三种颜色:

  • 白色:表示对象**尚未被垃圾收集器访问过****。**显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
  • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描 过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对 象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
  • 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

在这里插入图片描述

这里可能出现两种情况,本来应该删除的节点因为引用关系的改变导致没有删除继续存活,或者本来应该存活的节点因为引用关系的改变导致删除,第一种情况不过是在下一次检查的时候删除问题不大,但是第二种情况的问题非常严重,正常不应该出现这种情况

只有在下面来个条件都满足的时候才会导致**”节点消失“**(节点本来应该黑色但是标成白色)

  • 赋值器插入了一条或多条从黑色对象到白色对象的新引用;
  • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。因此,我们要解决并发扫描时的对象消失问题,只需破坏这两个条件的任意一个即可。由此分别产生了 两种解决方案:增量更新和原始快照。
  • 增量更新:当黑色对象插入新的指向白色对象的引用关系 时,就将这个新插入的引用记录下来,等并发扫描结束之后**,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。**
  • 原始快照:当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次

这里的插入和删除操作,虚拟机的记录都是通过写屏障完成的

总结

首先进行垃圾回收要通过可达性算法判断对象是否存活,同时垃圾回收要求在根节点枚举的时候暂停所有的用户线程(打扫房间的时候不能还有人在丢垃圾,其他人应该乖乖等着),**虚拟机需要快速确定对象的引用关系并且在必要时候为对象提供快照(这里使用了OopMap 实现),执行中的线程会在安全点中统一暂停,处于暂停中的线程会在安全区域中完成垃圾回收。**这里线程如何停顿已经解决了。

下面的问题是对象一旦跨代引用或者跨区引用,我们如果没有特殊方法可能需要将整个内存区域扫描,前面分代收集理论的时候也有提到过,记忆集的出现避免了扫描整个老年代,他的使用不光是在新生代,老年代中,在其他跨区域引用也可以使用,记忆集的精度如果太大可能导致占用太多内存,所以记忆集可以选择不同的精度,记录到一块内存区域精度的记忆集叫做卡表,卡表中记录的跨带的引用关系,卡表的出现就需要维护,在其他分代引用了本区域对象时,就需要使对应的卡表元素改变,这个改变的操作使用了写屏障,

最后是并发问题,在处理对象的回收时并发改变引用状态,可能导致本不应该回收的对象被回收,这里使用增量更新和原始快照的方法处理了这个问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/879459.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux操作系统入门(三)

_______________________________________________ 一.Linux操作系统的文件结构 相比于Windows操作系统的C,D,E等盘符,Linux操作系统仅有一个"/"符号的根目录. 这其中存在一个显著的不同,Linux操作系统使用的是斜杠"/",而Windows…

基于微信小程序的宠物之家的设计与实现

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSpringBootVueMySQL的宠物之家/宠物综合…

判断当前环境是否为docker容器下

判断当前环境是否为docker容器下 webshell后或登录到系统后台,判断是否为docker容器可使用如下方法: 方式一:使用ls -alh命令查看是否存在.dockerenv来判断是否在docker容器环境内 ls -alh /.dockerenv如下图无.dockerenv文件,所…

本地部署轻量级web开发框架Flask结合内网穿透公网环境访问管理界面

文章目录 1. 安装部署Flask2. 安装Cpolar内网穿透3. 配置Flask的web界面公网访问地址4. 公网远程访问Flask的web界面 本篇文章主要讲解如何在本地安装Flask,以及如何将其web界面发布到公网进行远程访问。 Flask是目前十分流行的web框架,采用Python编程语…

无人机飞手教员组装、调试高级教学详解

随着无人机技术的飞速发展,其在航拍、农业、救援、监测等多个领域的应用日益广泛,对专业无人机飞手的需求也随之增加。作为无人机飞手教员,掌握无人机的高级组装、调试技能不仅是教学的基础,更是培养学生成为行业精英的关键。本教…

【吊打面试官系列-Redis面试题】使用过 Redis 做异步队列么,你是怎么用的?

大家好,我是锋哥。今天分享关于【使用过 Redis 做异步队列么,你是怎么用的?】面试题,希望对大家有帮助; 使用过 Redis 做异步队列么,你是怎么用的? 一般使用 list 结构作为队列,rpus…

Word中插入当前日期与时间

Word中插入当前日期与时间 通过构建基块的方法快速插入当前日期与时间 快捷键操作 快捷键具体功能说明 Alt Shift D 插入当前日期date Alt Shift T 插如当前时间time Ctrl Shift F9 使得域文本变为正常文本 Ctrl F11 锁定域更新域菜单工具会变为黑色 C…

你的大模型应用表现真的好吗?借助 Dify + Langfuse 一探究竟

背景介绍 众所周知,大模型应用的输出存在着一些不确定性,往往需要迭代多轮才能得到较为稳定的输出结果,因此开发者往往需要关注大模型应用的实际表现,并进行有针对性的优化。 然而常规 Web 服务的监控机制往往无法满足大模型应用…

python绘制3d建筑

import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d.art3d import Poly3DCollection# 随机生成建筑块数据 def generate_building_blocks(num_blocks, grid_size100, height_range(5, 50), base_size_range(10, 30)):buildings []for _ in range(…

sqli-labs靶场自动化利用工具——第1关

文章目录 概要整体架构流程技术细节执行效果小结 概要 Sqli-Labs靶场对于网安专业的学生或正在学习网安的朋友来说并不陌生,或者说已经很熟悉。那有没有朋友想过自己开发一个测试脚本能实现自动化化测试sqli-labs呢?可能有些人会说不是有sqlmap&#xf…

中国矿业大学《2023年868+2007年自动控制原理真题》 (完整版)

本文内容,全部选自自动化考研联盟的:《25届中国矿业大学868自控考研资料》的真题篇。后续会持续更新更多学校,更多年份的真题,记得关注哦~ 目录 2007年复试真题 2023年初试真题 Part1:完整版真题 2007年复试真题 2…

【Python基础】Python错误和异常处理(详细实例)

本文收录于 《Python编程入门》专栏,从零基础开始,分享一些Python编程基础知识,欢迎关注,谢谢! 文章目录 一、前言二、Python中的错误类型三、Python异常处理机制3.1 try-except语句3.2 try-except-else语句3.3 try-fi…

TiDB 扩容过程中 PD 生成调度的原理及常见问题丨TiDB 扩缩容指南(一)

导读 作为一个分布式数据库,扩缩容是 TiDB 集群最常见的运维操作之一。本系列文章,我们将基于 v7.5.0 具体介绍扩缩容操作的具体原理、相关配置及常见问题的排查。 通常,我们根据当前资源状态来决定是否需要调整 TiKV 节点的规模&#xff0…

探索螺钉设计:部分螺纹与全螺纹,哪种更适合你的项目?

为什么有些螺钉有部分螺纹? 螺钉由头部、柄部和尖端组成,是世界上zui常用的紧固件之一。与螺栓一样,它们旨在将多个对象或表面连接在一起。但是,在比较不同类型的螺钉时,您可能会注意到其中一些都具有部分螺纹杆。 什么是螺柄&a…

Python | Leetcode Python题解之第397题整数替换

题目: 题解: class Solution:def integerReplacement(self, n: int) -> int:ans 0while n ! 1:if n % 2 0:ans 1n // 2elif n % 4 1:ans 2n // 2else:if n 3:ans 2n 1else:ans 2n n // 2 1return ans

Python_两个jpg图片文件名称互换

项目场景 处理Adobe Photoshop导出的两个切片的顺序错误问题 小编在进行图片切片处理的时候,发现用PS导出的切片顺序错误,例如用PS导出的切片分别为test_01.jpg,test_02.jpg,但实际的使用需求是将两个图片的顺序调换&#xff0c…

self-play RL学习笔记

让AI用随机的路径尝试新的任务,如果效果超预期,那就更新神经网络的权重,使得AI记住多使用这个成功的事件,再开始下一次的尝试。——llya Sutskever 这两天炸裂朋友圈的OpenAI草莓大模型o1和此前代码能力大幅升级的Claude 3.5&…

基于less和scss 循环生成css

效果 一、less代码 复制代码 item-count: 12; // 生成多少个 .item 类.item-loop(n) when (n > 0) {.icon{n} {background: url(../../assets/images/menu/icon{n}.png) no-repeat;background-size: 100% 100%;}.item-loop(n - 1);}.item-loop(item-count);二、scss代码 f…

【人工智能】Transformers之Pipeline(十七):文本分类(text-classification)

目录 一、引言 二、文本分类(text-classification) 2.1 概述 2.2 DistilBERT—BERT 的精简版:更小、更快、更便宜、更轻便 2.3 应用场景​​​​​​​ 2.4 pipeline参数 2.4.1 pipeline对象实例化参数 2.4.2 pipeline对象使用参数 …

【Hot100】LeetCode—287. 寻找重复数

目录 1- 思路题目识别快慢指针-类比链表判环 2- 实现⭐31. 下一个排列——题解思路 3- ACM 实现 原题链接:287. 寻找重复数 1- 思路 题目识别 识别1 :给定一个数组,寻找数组中的重复数。必须用 O(1) 的空间复杂度,且不能修改数组…