首先考虑一个问题:
接下来,让我们了解JVM如何将垃圾回收在一起。在此之前,默认虚拟机被同意为当前的主流热点。
只有当一个对象不再使用时,JVM才能回收它。那么如何确定物体是否幸存?
参考计数方法是一种非常经典的方法,其原理也非常简单,即维持每个对象的参考计数。每当引用对象时,参考数就会增加。当对象的参考计数为0时,这意味着对象没有引用将其确定为死亡状态。
参考计数的优点是它的实现很简单,并且确定效率也很高。例如,此方法用于python中的C ++或垃圾回收中的智能指针中,但是Java中几乎所有主流虚拟机都使用此简单方法。这是因为参考计数不能保证它是完全正确的,例如经典周期参考问题。
如果本地变量目前放弃对对象的引用,则两个堆叠的对象仍然无法回收,因为它们之间有相互的参考:
算法想。该算法是通过一系列节点依次使用的根对象的集合,称为“ GC根”。当GC根开始到对象的GC根开始时,不可能使用要再次使用的对象。
例如,上图中的对象5和移动6是死对象。通过可访问的分析算法,可以避免对象周期参考的问题。对于审查,仍然可以回收。
可以用作GC根的对象包括以下内容:
如果我们总结除了堆中的对象外,堆的对象将不用为GC根,但是将使用堆对象或常量池的对象,但这不是绝对的。何时将其转换为跨代参考。
算法步骤级别的分析主要采用三种颜色标记方法。根据是否访问对象,将其分为三种颜色:
整个算法都使用代码来指示以下算法(我模拟,非官方):
以上代码在单个线程模式下运行。所有节点都传播后,将获得结果。实际上,只需要两种颜色。但是JVM允许并发标记。在此时间,必须将灰色节点信息传递到其他线程,以防止其他线程直接标记节点。
即使一个物体被标记为“死亡”状态,它也不一定会回收,这涉及JVM回收垃圾的具体步骤。
要真正恢复垃圾,必须至少经历两个标记,并且第一次将三色标记运行对象,然后将对JVM进行一次筛选,以确定对象是否必须执行最终化方法。确定的基础是对象重写该方法的基础,并且没有执行此方法,如果没有必要执行,它将直接恢复;如果确实需要它,则将对象放置在F-Quesue队列中,而JVM为executeLater创建了一个低优先级线程,JVM将在队列中的对象上标记为小尺度标记。JVM统计数据是GC根的统计数据,以避免重新传播所有GC根。如果对象在最终化方法中重新关联参考链,则该对象将是对象。将第二个标记缩放并复活。
应该注意的是,JVM不会等待成功执行最终确定。相反,它将在短时间后标记。不管是否执行该方法,JVM都无法接受“垃圾”和疯狂的职业程序操作资源。此外,根据上述判断基础,一个对象无法重复复活,因为最终方法只能执行一次。
但是,最终化方法已被放弃,这将影响程序性能。尝试 - 最佳选择是一个更好的选择。
垃圾收集过程取决于引号,但是过去,一个物体被“引用”和“未引用”。一种现象相似。例如,您仍然有一些作业要写。您想完成主要任务,但是对于某些次要任务,您希望有时间是否有时间。漫画的选择,而不是必须处于必须处于的状态。我们希望在有空间时将其保留,否则清楚。为此,Java完善了参考的概念:
上述判断对象的生存过程称为标记过程。当我们标记一个对象时,我们将启动真正的垃圾回收过程。
但是,在此之前,我们应该谈论经典的代数假设理论。毕竟,所有垃圾回收算法均基于这些理论:
正是由于这些假设,JVM将Java物体分为新一代(年轻)和老年(旧)。所有对象最初都用作新一代(下面的一些大对象除外)。收集垃圾后,它将进入老年。新一代和老年已被放置在不同的地区,并且根据不同年龄的不同算法收集垃圾。
该算法分为两个部分:标记和清除。首先,标记所有不可接受的对象,然后直接清除这些对象。请注意,此处的删除不是真正空的,而是要取消分配状态,然后保存自由链接列表中的免费块信息,等待下一次分配。
拆卸操作类似于操作系统中的空闲空间分配和回收利用。读者可以理解空间空间分配算法。
标记算法的优点是它很简单,但是缺点也很明显。根据替代性低位症,大多数物体都死了,这意味着要标记和清除这些物体需要更多的时间,如上图所示,在回收后很容易引起外部片段,这可能使其难以使其困难对于下一个发行版。
可以清理标签方法,并可以用于清理旧一代,但是由于其各种缺点,标记方法很少单独使用。
标记复制方法解决了标记方法的缺点。它划分可用的内存,并根据容量将两块相等的尺寸划分。另一个人只使用其中一个,然后清理一次使用过的存储空间。
如果内存中的大多数对象都能生存,则该算法将产生大量的内存间接内存,但是对于大多数对象,可回收可回收,算法需要复制一些幸存的对象,并且每次是记忆时,在整个半区域进行回收。当分配内存时,无需考虑空间碎片的复杂情况。这是简单有效的,它几乎可以解决通过Mark -Craining方法带来的缺点。
它的缺陷也很明显。这种复制的成本和回收算法的成本是将可用记忆减少到原始的一半并浪费很多空间。为了解决此问题,IBM仔细研究了物体生存的比例,并发现大多数情况下,,只有2%的物体幸存。由于标签复制方法复制了生存对象,因此根本不需要遵循1:1的比例。要分配空间,因此有一种更优雅的标签复制方法,称为“ Appel Reclycling方法”。该算法将根据8:1:1的比率分割新一代区域。
具体而言,新一代分为一个伊甸园和两个幸存者地区。伊甸园区域和幸存者区域的尺寸比率为8:1。当我们分配空间时,我们仅使用伊甸园和一个幸存者区域之一保留另一个生存的生存空间,当垃圾回收启动时,它将被复制为分配空间的伊甸园和另一个幸存者地区的幸存者地区之一,然后清除伊甸园和一个幸存者地区之一(包括物体的不稳定区域),现在使用了幸存者作为该区域的副本,等待下一个回收...
在并发副本期间,内存的分配将暂时放置在幸存区域,这确保在副本阶段不会出现错误。
但是,应注意的是,并非所有程序都可以确保可以在幸存者区域安装生存对象。发生这种情况时,必须将其用作老年帮助的保证。标记复制方法通常仅用于恢复新一代。Appel回收方法”。
这里还有另一个问题,如何解决大物体?较大对象的复制非常慢,可能会导致很长时间停止并给用户带来糟糕的体验。对于此问题,大多数JVM指定超过一定大小的对象将直接进入老年。
在老年时,没有额外的空间保证,并且生存物体太大,复制效率较低,并且无法使用标签算法。通常,使用标记汇编方法。
由于没有额外的空间,因此只能在老年中实现。为了避免产生大量的外部片段,例如去除方法,标记组织方法将生存的对象移至空间的一侧。
由于JVM是根据页面实现的,因此标记组织算法的实现相对简单。生存对象可以直接覆盖死亡点。尽管它很简单,但它是由大量记忆或磁盘读取和编写的。性质(有许多生存对象),这将非常慢(我们不能将死亡对象移出,因为在这种情况下,生存对象中仍然有很多碎片)。在处理过程中,过程JVM不能支持分配对象,因此所有线程都必须悬挂(ZGC都会打破笼子)。由于悬挂时间很长,因此对设计师形象的描述被描述为“停止世界”。
标记整理的优点和缺点。当使用取决于特定环境时。
JVM主动倡议几乎是新一代的所有垃圾回收,只有在空间不足时,它才会启动老年恢复或
虚拟机遍历所有堆栈空间和方法区域以确定GC根吗?这种效率太低。热点虚拟机使用一组数据结构OOPMAP(对象指针)。对于对象之间的引用,虚拟机将在加载虚拟机后计算对象中的所有数据。如果是参考类型,则将其存储在OOPMAP中;要引用堆栈TOWAT阶段(在运行时),请扫描所有堆栈帧之间的数据,然后将引用保存到OOPMAP。然后,JVM可以直接扫描OOPMAP来确定GC根。
来考虑OOPMAP是否可以与用户线程同时运行。答案还可以,但是实际成本太高,导致几乎所有主流虚拟机都暂停了用户线程生成的OopMap来启动垃圾收集。显然,即时编译器将在所有堆栈框架和堆栈框架和考虑并发的形式。当程序输入方法时,即时编译器开始收集当前堆栈帧的所有信息。收集信息后,该程序目前为此。目前,该程序已收集。目前,收集程序。它恰好退出了方法区域,这意味着堆栈框架信息已更改。此方法不在堆栈中,这会导致即时编译器收集无用甚至错误的信息。错误的信息使成本太昂贵。
因此,在开始垃圾收集之前,必须将“ STW”暂停以确定GC根,这引入了另一个问题,该问题是在程序代码中暂停或暂停?
以上解释了暂停执行的必要性。暂停的原因之一是确保垃圾收集的安全性,因此程序暂停的位置是安全点。在设定安全点的情况下,现在可以添加一个新规则:必须将程序执行到启动垃圾收集的安全点,不要随意启动。此规则意味着一条信息,也就是说,必须在存储器代码的分配中设置一个安全点,以防止内存启动垃圾回收。
那么,其他地方除了在启动内存分配时设置安全点外,还应设置安全点?它不能允许程序执行太长,这可能导致内存占用率飙升,也不能经常实施,否则会影响性能。
基本上根据程序选择安全点的选择是“长时间过程的特征” - 因为执行每个指令的时间非常短,并且该程序不太可能,因为指令流量长度也是如此因此,“长期执行”的最明显功能是重复使用的指令序列,例如方法调用,周期跳跃,异常跳跃等。因此,使用这些功能的指令将生成SafePoint。
这是书中的原始文字。可能很难理解。翻译是为了避免长期无法进入安全点。程序的情况。例如,对于超大周期,它一定不能在周期中设置一个安全点,否则它将以频繁的周期实现安全点。因此。
安全点通常由前端编译器生成。进入JVM之前,安全点已固定。
通常,您将在以下职位上选择一个安全点:
现在的问题是,因为GC需要在发生GC时暂停线程(不仅是单个线程收集器,甚至是多线收集器,也需要在某个阶段停止线程),那么当GC在点上发生GC时再次?
有两种主要方法:
什么是安全区?
安全点是为正在执行的线程设置的。如果线程处于睡眠状态或中断状态,则无法响应JVM的中断请求,然后运行到安全点。在代码片段中,参考关系不会更改。GC可以安全地在该区域的任何位置启动GC。线程进入安全区域时,线程首先标记为已进入安全区域。当您准备离开安全区域时,请检查是否可以离开GC。直到安全离开的信号。
这意味着休眠或中断代码必须标记为安全区域。安全区域可以是代码(指令)或多个代码(指令)。当安全区域的边界时,您必须确定GC是否正在进行,如果是的,则不允许离开安全区域。
引入安全点后,您可能会认为当线程运行到安全点时,将发生GC,但实际上并非如此。安全点和GC是必要的。除非您手动致电。
对于经典的垃圾收集器系列,通常足以检测新一代的记忆是否足够,在分配内存时。检查是否有老年的空间。如果老年没有空间,将启动整个回收(全GC)。如果不起作用,它将是异常的。
让我们回到标记阶段,现在让我们考虑一个新问题:跨代引用问题。顾名思义,旧一代的年龄引用了新一代的新一代(通常是报价新一代在旧一代中,因为GC几乎是新一代开始的)。考虑到下面的情况,JVM通常只携带新一代。为了节省性能,JVM不会将GC根扫描到旧一代,而只能对GC根的可访问性分析指向新一代。如果是这样,就是这样。
但是,如果新一代所有旧一代或所有GC根都回收了,那么价格非常昂贵。为此,官员提出了记忆收集的概念。内存收集是从非收集区域到收集区域的录音。带有指针收集的数据结构。此外,内存收集将记录从老年人到新一代的跨代指针。
内存收集是一种抽象的数据结构,并且卡表是特定的。还有其他不同的实现,但是卡表目前已经成为主流。
在热点虚拟机中,卡表是一个字节数组,每个数组都对应于内存中的连续地址。如果该区域中有一个指向要回收区域的对象的引用,则卡表阵列对应于卡表阵列对应的阵列对应于tothe元素,将其放置为1,而没有位置为0;例如,以下代码,某个内存的地址向右移动(相当于512),以定位卡表元素,也就卡表区域。如果卡表的相应元素为1,则表示指向512字节所在区域中的指针。
卡表非常准确地到一个内存区域。只要在此内存区域中有一个跨代参考,相应的卡表将设置为1,并且对象没有准确的对象(卡片表指向对象,并且有一个跨代参考在对象中。设置的原因1)是该对象通常较小且更多,这意味着需要更多的卡表项来增加空间成本。根据代际假设,跨代的参考通常很小,因此放松一点有点放松。保存记忆是可以接受的。现在,在GC的开头,JVM还将检查卡表。如果卡表为1,则将遍历相应的存储区域,找到相应的横构代参考,然后将其添加到GC根中。
注意问题,即使像下图一样,也没有对OBJ 3的实际参考。目前,OBJ 3仍将添加到GC根部,因此OBJ 2不会回收,但这是可以接受,因为OBJ 2很快就很快就很快就会进入老人,跨代的参考将不再存在,OBJ 2和OBJ 3将一起回收。
那老年人的回收如何呢?老年人一定有跨代的引用,但实际上,这个问题得到了解决。因为当旧一代推出时,通常有必要启动新一代的回收利用(因此称为全GC),以及新一代新一代的回收。目前,检查是否有跨代参考,并将其添加到旧的GC根中。
我们希望让GC进程使用用户线程进行分析。
我们主要分析了同时分析的配对分析,并且在对JVM的深度理解中有一个非常生动的图片:
如果最终未删除本应删除的对象,则可以接受这种情况,并且可以通过下一次GC进行回收;但是对于本应意外幸存的物体来说,这是不可接受的。
查看最后两张图片,黑色节点添加了一个参考链,但是由于灰色节点删除了参考链,因此无法访问白节点。现在,用户需要致命错误的对象正式说,这种情况是“对象消失了”。
威尔逊的理论上证明了1994年,当同时满足以下两个条件时,将产生“对象消失”的问题,即应该被误认为是白色的黑物体:
因此,我们需要解决当并发扫描的对象消失的问题,只需销毁这两个条件中的任何一个。
这是书中的原始文字。实际上,它也非常简单。增量更新意味着重新扫描用户更新。查看已更新的引用。没有更改原始状态,仅执行附加更新,即增量更新;原始快照对已删除的引用进行了回顾,该引用可以抽象为未删除原始链,也就是说,一切仍然是原始状态。
当辅助扫描(更新或原始快照)时,它不再与用户一起运行,否则会落入死周期。因此,在这里并不是绝对同时发生,但是第二次扫描的时间相对较短,并且可以接受。
现在还有另一个问题,如何在并发过程中记录参考链的新增量或旧删除。在发现此问题时,JVM相对成熟,很难更改原始体系结构。因此,您需要考虑代理模式或装饰。在模式下,JVM使用动态代理技术,即写障碍。
有必要了解,参考的增加或删除是对象和对象之间的。堆栈上的本地参考不会引起并发分析的问题。
现在,通过JVM插入字节代码,新操作可能与下面相同(用于删除和参考):
不要感到惊讶,JVM有很长的时间插入字节代码,并且忘了提到卡片桌子的污垢也基于写入障碍。
研究理论之后,是时候看到特定的实现了。
吞吐量是指使用用户应用程序线程时程序总使用的比例,即:$ $ theput = frac {运行用户代码时间} {运行用户代码时间 +垃圾收集时间} $$
串行收集器是客户端模式下的默认新一代收集器。它也是最基本和最古老的收藏家。收藏家是一个串行收集器,这意味着在垃圾收集开始时必须悬挂所有线程。
串行收集器采用新一代的标记副本方法。当GC时,所有用户都必须在安全点停止。正如我们之前说的那样,当幸存者地区还不够时,连环收集者试图在老年时使用该空间。ifif没有足够的空间适合老年,它是由完整的GC完成的。完整的GC由CMS收集器或串行旧收藏家完成。
串行收集器的优势是简单有效的,并且占据了很小的记忆,但是它会带来很长时间的停顿,但是对于客户而言,此停顿是可以接受的。到目前为止,串行收集器仍然是在客户端模式下默认新一代收集器的热点。
Parnew收藏家只是串行收藏家的升级版本。Parnew Collector多线程并发集合,但不幸的是,这里的并发并不指用户线程的联合操作。在多个线程中。我们同时讨论了平行性分析。在那本时间,帕纳收藏家和收集的概念很明确。
应该注意的是,并发收集的效率不一定高于串行串行收集器。如果仅是由单个核心模拟的并发性,则最好直接序列化。毕竟,线程开关也很重。但是,如果处理器的许多内核,并发收集的效率将显着高于串行集合。
平行的清除收藏家与Parnew收藏家非常相似。平行的清除收藏家也被暂停。基于标签复制方法的多线程收集方法,评估并行清除收集器是一个全自动的,吞吐量的优先收集器。
我们提到了吞吐量的概念。并行的清除收集器允许用户指定一个比例。标签复制方法的效率早些时候已引入。当我们改善伊甸区的空间时,下一个回收利用可以在短时间内收回更多垃圾,从而增加吞吐量。
平行清除收集器是服务机下运行效率良好的收集器。
Serialold收藏家是一个较老的收藏家。这个收藏家也是串行的。在基于标记整理方法中回收时,也有必要“ stw”。
并行旧收集器是服务器上的可选较旧收集器。收集器的目的是改善完整GC的吞吐量。为了实现此目的,平行的旧收藏家使用并发标记 - 组织方法清理旧旧的旧菜单以清理年龄的老年人,请注意,在这里仍然需要暂停用户线程,而同意只是为了加快清洁过程,而不是减少暂停。在多核处理下,此方法确实可以提高全GC速度并增加吞吐量。
CMS是一位老年收藏家。CMS首次几乎已经意识到与用户的并发操作,但是当时,它尚未解决如何实现真正的并发标记汇编方法。因此,尽管CMS可以减少暂停,但它仍然会带来许多外部片段。CMS的注意力很低,CMS希望允许用户线程尽可能多地完成GC。
CMS操作分为4个步骤:
同时收集时,CMS必须为所有时间用户程序分配分配一些空间。CMS默认值将这些空间标记为生存,这意味着该部分空间产生的废物必须等到下一个集合。这是如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼如此称呼“浮动垃圾”,当保留空间的这部分拥挤时,CMS将停止所有作业,启动串行/串行旧收集器以启动完整的GC。当程序的核心还不够时,CMS会缓慢运行,这可能导致保留空间不足。最后,有必要致电串行收集器使其变得笨拙。CMS仅建议处理器核心> = 4。
G1(垃圾首先)垃圾收集器是当今垃圾回收技术中最切割的结果之一。就像JDK7一样,他已经加入了JVM Collector家族,并已成为Hotspot开发的关键垃圾回收技术。垃圾回收商G1也是一种关注最低小时的垃圾恢复装置。它也适用于集体垃圾收集大型内存。该官员还建议使用G1代替CM。G1的最大特征是引入分区,削弱世代的概念以及合理地使用垃圾收集每个周期中的资源,解决其他收藏家甚至CMS的许多缺陷。
G1垃圾收集器的设计原理是“首先收集了许多垃圾”,其目标是试图缩短通过加工大量(超过4GB)而产生的停顿。
因此,G1不等待完整的内存(例如串行收集器,并行收集器)或即将耗尽的时。取而代之的是,灵感算法被内部采用。收集()收集。
G1稀释了世代的思想。它将整个堆划分为相同大小的区域,但G1并没有放弃世代的想法。对于每个地区,G1仍然标志着其年龄,并根据不同年龄的年龄采用不同的算法。值得注意的是,值得注意。是的,G1将分别分区,即避免缓慢处理大物体标记的巨大区域。这种分区不需要在物理空间中连续,只有逻辑是必需的,这意味着G1可以动态调整大小。如果新一代空间不够,G1将分配空闲区域并将其整理成逻辑连续块。这使G1的内存分布率更高,此方法消除了外部片段,并且可能只有一些内部片段。
G1期望“建立可预测的暂停时间模型”,这意味着G1可以支持不超过N毫秒的决策,这与平行的清除收藏家不同。尽力确保实施。
G1的集合基于该地区的回收利用。G1可以为每个地区的状态增加补充。G1跟踪每个地区垃圾积累的价值。在以下情况下,G1将优先回收可以回收最大空间的区域。以前,GC由于老年而被启动了很长时间,而G1完全取消了该模型。当G1选择回收利用时,它不再选择回收利用。选择新一代是优先事项(但是,当桩空间较大时,新一代仍然优先考虑),而是根据预测的模型选择分区回收,这使得新一代是优先的。一个长的“ stw”。
如何确定该区域可回收垃圾的价值是设计G1的困难。G1计算所有可能的选项,例如上次回收之前卡片表的肮脏页面数量,上一个回收区的代际和使用空间的大小,然后是Space的大小。通过先前的回收和回收空间的空间,然后基于下一次恢复之前的数据统计数据,预测下一次回收时间比我们想象的要复杂。
G1的分区分为卡表和512个字节。G1卡表更复杂。G1卡表更像是地图。关键是指向区域的区域。例如,下图中的地图可能存储键= addr(区域1)和region 2区域1中的索引编号,也就是说,2。此卡表占据更大的空间,但更详细的信息有利于每个分区的并发收集。
G1的垃圾回收包括以下回收机制:
G1几乎是“无所不能”的垃圾收集器,但G1取决于计算机资源。每个分区中只存储的卡表可以占总内存的15%。通常,当内存> = 16G时,内核编号> = 8时,G1的使用将具有显着提高的效率。
对G1的理解主要来自分散的信息统计数据。