导航菜单

咱们从头到尾说一次 Java 垃圾回收

91真钱捕鱼游戏

  18:19:46CSDN

  

作者|率鸽

编辑|胡雪芮

在我上学之前,我有一个这样的人。我说我在自助餐厅吃饭。吃完菜后,我是一名C ++程序员。吃完饭之后,我还是一名Java程序员。

实际上,在Java世界中,似乎我们不必专注于垃圾收集。许多初学者不了解GC,仍然可以编写一个更好的程序或系统。但实际上这并不意味着Java的GC并不重要。相反,存在问题是如此重要和复杂。那些初学者除了打开GC日志并查看一堆0101天文学外无法做任何事情。

今天我们将从头到尾谈论Java垃圾收集。

什么是垃圾收集

垃圾收集(GC),顾名思义,是释放垃圾占用的空间,以防止内存泄漏。有效利用可用内存来清理和回收已在内存堆中死亡或长时间未使用的对象。

在Java语言出现之前,每个人都在拼命编写C或C ++程序。这时,存在很大的矛盾。 C ++和其他语言创建对象应该不断打开空间。不使用时,他们需要不断释放控件。构造函数和析构函数都被写入,并且多次重复,然后被破坏。因此,有人建议你可以编写一个程序来实现这个功能。每次创建和释放控件时,都可以重复使用此代码而无需重复。

1960年,基于麻省理工学院的Lisp首次提出了垃圾收集的概念,而Java还没有诞生!所以事实上GC并不是Java的专利,GC的历史远远大于Java的历史!

如何定义垃圾

由于我们要进行垃圾收集,我们必须首先弄清楚垃圾的定义是什么以及需要回收哪些内存。

参考计数算法

ReachabilityCounting算法通过在对象头中分配空格来保存引用对象的次数(ReferenceCount)。如果该对象被另一个对象引用,则其引用计数将增加1.如果删除对该对象的引用,则其引用计数减1.当对象的引用计数为0时,该对象将被回收。

STRING公尺=newString( '' 插孔 '');

首先创建一个字符串,这次''jack''有一个引用,即m。

然后将m设置为此时,对''jack''的引用数等于0.在引用计数算法中,这意味着需要回收内容。

米=;

引用计数算法将垃圾收集分发到整个应用程序,而不是暂停垃圾收集,直到堆中所有对象的处理结束。因此,使用引用计数的垃圾收集并不严格地是“停止世界”的垃圾收集机制。

它似乎非常好,但我们知道JVM的垃圾收集是“停止世界”。我们最终放弃了引用计数算法的原因是什么?看下面的例子。

publicclassReferenceCountingGC {

publicObjectinstance;

publicReferenceCountingGC(字符串名称){}

}

publicstaticvoidtestGC {

ReferenceCountingGCa=newReferenceCountingGC( '' objA '');

ReferenceCountingGCb=newReferenceCountingGC( '' objB '');

A.instance=B;

B.instance=一个;

A=;

B=;

}

1.定义2个对象

2.相互引用3.清空相应的声明引用

我们可以看到,最终,这两个对象不再可访问,但由于它们相互引用,它们的引用计数永远不会为0.通过引用计数算法,它们将永远不会被GC通知。回收它们。

可访问性分析算法

ReachabilityAnalysis的基本思想是使用一些称为参考链(GCRoots)的对象作为从这些节点向下搜索的起点。搜索路径被调用(ReferenceChain),当某个对象未通过任何参考链连接到GCRoots时(即,从GCRoots节点到该节点无法访问),该对象被声明为不可用。

通过可达性算法,成功解决了引用计数无法解决的问题 - “循环依赖”。只要您无法与GCRoot建立直接或间接连接,系统就会确定您是可回收对象。这引出了另一个问题,即GCRoot。

Java内存区域

在Java语言中,以下四种类型的对象可用作GCRoot:

虚拟机堆栈中引用的对象(堆栈框架中的局部变量表)

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

方法区域中常量引用的对象

JNI引用的对象(即一般的Native方法)在本地方法堆栈中

虚拟机堆栈中引用的对象(堆栈框架中的局部变量表)

这时,s是GCRoot。当s设置为null时,localParameter对象也会使用GCRoot中断参考链,并将被回收。

publicclassStackLocalParameter {

publicStackLocalParameter(字符串名称){}

}

publicstaticvoidtestGC {

StackLocalParameters=newStackLocalParameter( '' localParameter '');

S=;

}

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

s是GCRoot,s设置为,在GC之后,s指向的属性对象可以被回收,因为它无法与GCRoot建立关系。

而m作为类的静态属性,也属于GCRoot,参数对象仍与GCroot连接,因此此时参数对象不会被回收。

publicclassMethodAreaStaicProperties {

publicstaticMethodAreaStaicPropertiesm;

publicMethodAreaStaicProperties(字符串名称){}

}

publicstaticvoidtestGC {

MethodAreaStaicPropertiess=newMethodAreaStaicProperties( '' 属性 '');

S.M=newMethodAreaStaicProperties( '' 参数 '');

S=;

}

方法区域中常量引用的对象

m是方法区域中的常量引用。它也是GCRoot。设置s后,最终对象将不会被回收,因为它未与GCRoot连接。

publicclassMethodAreaStaicProperties {

publicstaticfinalMethodAreaStaicPropertiesm=MethodAreaStaicProperties( '' 最终 '');

publicMethodAreaStaicProperties(字符串名称){}

}

publicstaticvoidtestGC {

MethodAreaStaicPropertiess=newMethodAreaStaicProperties( '' staticProperties '');

S=;

}

本地方法堆栈中引用的对象

任何本机接口都将使用某种本地方法堆栈。如果使用C连接模型实现本地方法接口,则其本地方法堆栈是C堆栈。当线程调用Java方法时,虚拟机会创建一个新的堆栈帧并将其推送到Java堆栈中。但是,当它调用本地方法时,虚拟机会保持Java堆栈不变,不再将新帧推送到线程的Java堆栈中。虚拟机只是动态连接并直接调用指定的本地方法。

如何回收垃圾

在确定哪些垃圾可以回收之后,垃圾收集器必须开始垃圾收集,但是存在与如何有效地回收垃圾有关的问题。由于Java虚拟机规范没有明确定义如何实现垃圾收集器,因此来自不同供应商的虚拟机可以以不同方式实现垃圾收集器。在这里,我们讨论几种常见垃圾收集算法的核心思想。

马克---清算法

Mark-Sweep算法是最基本的垃圾收集算法。它分为两部分。它首先在内存区域标记这些对象,这些对象是可回收的标记,然后清除它们。如上所示,清理后的垃圾成为未使用的存储区域,等待再次使用。

这种逻辑不再清晰。它运行良好,但它有一个很大的问题,那就是内存碎片。

上图中中间块的假设是2M,较小的是1M,较大的是4M。当我们回收它时,内存将被分成许多段。我们知道,当我们打开内存空间时,我们需要一个连续的内存区域。此时,我们需要一个2M的内存区域,并且有两个1M无用。这导致我们拥有如此多的内存,但我们无法使用它。

复制算法

复制算法(Copying)从标记清除算法演化而来,解决了标记清除算法的内存碎片问题。它将可用内存划分为两个大小相等的块,一次只使用其中一个。当该块的内存用完时,将幸存的对象复制到另一块,然后清理一次使用过的内存空间。它确保了内存的连续可用性,并且在分配内存时不需要考虑诸如内存碎片之类的复杂情况。逻辑清晰,操作高效。

上面的图片非常清楚,很明显已经暴露出另一个问题。与我的140平大三居室一起,它只能用作70平方米的小型两居室房子。价格太高了。

标记算法

Mark-Compact标记过程仍然与标记---清除算法相同,但后续步骤不直接清理可循环使用的对象,而是将所有幸存的对象移动到一端,然后清除结束边界。记忆区。

一方面,标记算法在标记清除算法上升级,以解决内存碎片问题,并且还避免了复制算法只能使用一半内存区域的缺点。它看起来不错,但是从上图中可以看出,它更频繁地更改内存,并且需要对所有幸存对象的引用地址进行排序,这比复制算法要糟糕得多。

世代收集算法(GenerationalCollection)不是严格意义或理论,而是上述三种基本算法思想的组合,以及针对不同情况的一组组合拳。对象生命周期的差异将内存分成几个部分。一般来说,Java堆分为新一代和老一代,因此可以根据每个时代的特点采用最合适的收集算法。在新一代中,每次垃圾收集发现大量对象死亡,只有少量生存,那么使用复制算法,只需要支付少量幸存对象复制成本即可完成收集。在过去,由于物体存活率很高并且没有额外的空间来分配它,它必须通过使用标记清洁或标记来回收---------所以,另一个问题来了,什么块的种类是划分的内存区域,每个块的特殊算法是什么?

记忆模型和回收策略

Java堆(JavaHeap)是JVM管理的最大内存块。堆是垃圾收集器管理的主要区域。这里我们主要分析Java堆的结构。

Java堆主要分为两个领域 - 年轻一代和老年人。年轻一代分为伊甸园区和幸存者区。幸存者区域分为From和To2区域。也许在这个时候每个人都会有疑问,你为什么需要幸存者区,为什么幸存者必须分成两个区域。别担心,我们从头到尾看它,看看对象是如何产生的,以及它为什么没有。

伊甸园区

小发猫的专业研究表明,近98%的对象都在死亡,因此在这种情况下,在大多数情况下,当Eden区域内没有足够的空间用于分发时,对象将被分配到新一代的Eden区域。机器启动MinorGC,这比MajorGC更频繁,并且更快地回收。

在MinorGC之后,伊甸园将被清空,伊甸园地区的大部分物品将被回收,而那些不需要回收的物品将进入幸存者的From区域(如果From区域不够,则直接进入旧区)。

幸存者区

幸存者区域相当于伊甸园和旧区域的缓冲区,类似于交通信号灯中的黄灯。幸存者分为两个区域,一个是From区域,另一个是To区域。每次执行MinorGC时,Eden区域和From幸存对象都被放置在Survivor的To区域中(如果To区域不够,它将直接进入Old区域)。

你为什么需要它?

难道不是老一辈的新一代,直接去老,这是如此的好,它是如此复杂。如果您没有幸存者区域,那么每次您在伊甸园区域进行MinorGC时,幸存的物体将被送到老年人,并且很快就会被填满。有很多对象,虽然MinorGC还没有被淘汰,但它不会持续很长时间。也许第二次,第三次需要清除。在这个时候,进入老年显然不是一个明智的决定。

因此,幸存者的存在是减少发送到老年的对象数量,从而减少MajorGC的发生。幸存者的预筛选保证只有那些在新一代MinorGC中幸存16次的受试者才会被送到老年。

你为什么需要两个?

设置两个Survivor区域的最大好处是解决内存碎片问题。

让我们首先假设如果只有一个区域,幸存者会发生什么。执行MinorGC后,Eden区域被清空,幸存的物体被放置在Survivor区域,并且可能需要清除前一个Survivor区域中的一些物体。问题即将来临,我们如何在此时清除它们?在这种情况下,我们只能标记清理,并且我们知道标记的最大问题是内存碎片。在经常死亡的新一代中,使用标记将不可避免地导致严重的内存碎片。因为幸存者有2个区域,所以MinorGC每次将之前的Eden和From区域中的幸存物体复制到To区域。在第二个MinorGC中,From和To职责被赎回。此时,Eden和To区域中的幸存对象将再次复制到From区域。

这种机制的最大优点是在整个过程中总有一个Survivorspace为空,而另一个非空的Survivorspace是无碎片的。那么,为什么幸存者不会分割更多的碎片呢?例如,如果将幸存者区域细分,则每个部分的空间将很小,这将很容易导致幸存者区域。两个幸存者区域可能是称重后的最佳解决方案。

旧区

老年人占据堆内存空间的2/3,只有在MajorGC期间才会被清理,每次GC都会触发“Stop-The-World”。内存越大,STW越长,因此内存不仅更大,而且更好。由于复制算法在对象存活率高的时候执行了多次复制操作,效率很低,所以这里的老年人就是标记---整理算法。

除此之外,在内存保证机制下,无法放置的对象将直接进入老年,以下情况将进入老年。

大对象

大对象是指需要大量连续内存空间的对象。无论是否“死亡”,这部分目标将直接进入老年。这主要是为了避免Eden区域和两个Survivor区域之间的大量内存复制。当你的系统有许多“死在死里”的大物时,你必须要注意。

长期存活的物体

虚拟机为每个对象定义对象年龄(Age)计数器。在正常情况下,物体将继续在幸存者的From和To区域之间移动。对于幸存者区域中的每个MinorGC,该主题将增加1年。当年龄增加到15岁时,它将转移到老年。当然,15,JVM在这里也支持特殊设置。

动态对象年龄

虚拟机没有注意对象年龄必须是15岁的要求,并且将被放入老年。如果幸存者空间中同一年龄的所有物体的总大小大于幸存者空间的一半,则年龄大于或等于此年龄的物体可以直接进入老年。区,无需等待你“成人”。

这实际上有点像负载平衡。轮询是一种负载平衡,确保每台机器获得相同的请求。它似乎很平衡,但每台机器的硬件都不合理,而且健康也不同。我们还可以根据每台机器收到的请求数或每台机器的响应时间来调整负载均衡算法。

作者简介:聂小龙(花名:率鸽),阿里巴巴高级开发工程师。本文由作者提交,版权归作者所有。

【END】

作者|率鸽

编辑|胡雪芮

在我上学之前,我有一个这样的人。我说我在自助餐厅吃饭。吃完菜后,我是一名C ++程序员。吃完饭之后,我还是一名Java程序员。

实际上,在Java世界中,似乎我们不必专注于垃圾收集。许多初学者不了解GC,仍然可以编写一个更好的程序或系统。但实际上这并不意味着Java的GC并不重要。相反,存在问题是如此重要和复杂。那些初学者除了打开GC日志并查看一堆0101天文学外无法做任何事情。

今天我们将从头到尾谈论Java垃圾收集。

什么是垃圾收集

垃圾收集(GC),顾名思义,是释放垃圾占用的空间,以防止内存泄漏。有效利用可用内存来清理和回收已在内存堆中死亡或长时间未使用的对象。

在Java语言出现之前,每个人都在拼命编写C或C ++程序。这时,存在很大的矛盾。 C ++和其他语言创建对象应该不断打开空间。不使用时,他们需要不断释放控件。构造函数和析构函数都被写入,并且多次重复,然后被破坏。因此,有人建议你可以编写一个程序来实现这个功能。每次创建和释放控件时,都可以重复使用此代码而无需重复。

1960年,基于麻省理工学院的Lisp首次提出了垃圾收集的概念,而Java还没有诞生!所以事实上GC并不是Java的专利,GC的历史远远大于Java的历史!

如何定义垃圾

由于我们要进行垃圾收集,我们必须首先弄清楚垃圾的定义是什么以及需要回收哪些内存。

参考计数算法

ReachabilityCounting算法通过在对象头中分配空格来保存引用对象的次数(ReferenceCount)。如果该对象被另一个对象引用,则其引用计数将增加1.如果删除对该对象的引用,则其引用计数减1.当对象的引用计数为0时,该对象将被回收。

STRING公尺=newString( '' 插孔 '');

首先创建一个字符串,这次''jack''有一个引用,即m。

然后将m设置为此时,对''jack''的引用数等于0.在引用计数算法中,这意味着需要回收内容。

米=;

引用计数算法将垃圾收集分发到整个应用程序,而不是暂停垃圾收集,直到堆中所有对象的处理结束。因此,使用引用计数的垃圾收集并不严格地是“停止世界”的垃圾收集机制。

它似乎非常好,但我们知道JVM的垃圾收集是“停止世界”。我们最终放弃了引用计数算法的原因是什么?看下面的例子。

publicclassReferenceCountingGC {

publicObjectinstance;

publicReferenceCountingGC(字符串名称){}

}

publicstaticvoidtestGC {

ReferenceCountingGCa=newReferenceCountingGC( '' objA '');

ReferenceCountingGCb=newReferenceCountingGC( '' objB '');

A.instance=B;

B.instance=一个;

A=;

B=;

}

1.定义2个对象

2.相互引用3.清空相应的声明引用

我们可以看到,最终,这两个对象不再可访问,但由于它们相互引用,它们的引用计数永远不会为0.通过引用计数算法,它们将永远不会被GC通知。回收它们。

可访问性分析算法

ReachabilityAnalysis的基本思想是使用一些称为参考链(GCRoots)的对象作为从这些节点向下搜索的起点。搜索路径被调用(ReferenceChain),当某个对象未通过任何参考链连接到GCRoots时(即,从GCRoots节点到该节点无法访问),该对象被声明为不可用。

通过可达性算法,成功解决了引用计数无法解决的问题 - “循环依赖”。只要您无法与GCRoot建立直接或间接连接,系统就会确定您是可回收对象。这引出了另一个问题,即GCRoot。

Java内存区域

在Java语言中,以下四种类型的对象可用作GCRoot:

虚拟机堆栈中引用的对象(堆栈框架中的局部变量表)

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

方法区域中常量引用的对象

JNI引用的对象(即一般的Native方法)在本地方法堆栈中

虚拟机堆栈中引用的对象(堆栈框架中的局部变量表)

这时,s是GCRoot。当s设置为null时,localParameter对象也会使用GCRoot中断参考链,并将被回收。

publicclassStackLocalParameter {

publicStackLocalParameter(字符串名称){}

}

publicstaticvoidtestGC {

StackLocalParameters=newStackLocalParameter( '' localParameter '');

S=;

}

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

s是GCRoot,s设置为,在GC之后,s指向的属性对象可以被回收,因为它无法与GCRoot建立关系。

而m作为类的静态属性,也属于GCRoot,参数对象仍与GCroot连接,因此此时参数对象不会被回收。

publicclassMethodAreaStaicProperties {

publicstaticMethodAreaStaicPropertiesm;

publicMethodAreaStaicProperties(字符串名称){}

}

publicstaticvoidtestGC {

MethodAreaStaicPropertiess=newMethodAreaStaicProperties( '' 属性 '');

S.M=newMethodAreaStaicProperties( '' 参数 '');

S=;

}

方法区域中常量引用的对象

m是方法区域中的常量引用。它也是GCRoot。设置s后,最终对象将不会被回收,因为它未与GCRoot连接。

publicclassMethodAreaStaicProperties {

publicstaticfinalMethodAreaStaicPropertiesm=MethodAreaStaicProperties( '' 最终 '');

publicMethodAreaStaicProperties(字符串名称){}

}

publicstaticvoidtestGC {

MethodAreaStaicPropertiess=newMethodAreaStaicProperties( '' staticProperties '');

S=;

}

本地方法堆栈中引用的对象

任何本机接口都将使用某种本地方法堆栈。如果使用C连接模型实现本地方法接口,则其本地方法堆栈是C堆栈。当线程调用Java方法时,虚拟机会创建一个新的堆栈帧并将其推送到Java堆栈中。但是,当它调用本地方法时,虚拟机会保持Java堆栈不变,不再将新帧推送到线程的Java堆栈中。虚拟机只是动态连接并直接调用指定的本地方法。

如何回收垃圾

在确定哪些垃圾可以回收之后,垃圾收集器必须开始垃圾收集,但是存在与如何有效地回收垃圾有关的问题。由于Java虚拟机规范没有明确定义如何实现垃圾收集器,因此来自不同供应商的虚拟机可以以不同方式实现垃圾收集器。在这里,我们讨论几种常见垃圾收集算法的核心思想。

马克---清算法

Mark-Sweep算法是最基本的垃圾收集算法。它分为两部分。它首先在内存区域标记这些对象,这些对象是可回收的标记,然后清除它们。如上所示,清理后的垃圾成为未使用的存储区域,等待再次使用。

这种逻辑不再清晰。它运行良好,但它有一个很大的问题,那就是内存碎片。

上图中中间块的假设是2M,较小的是1M,较大的是4M。当我们回收它时,内存将被分成许多段。我们知道,当我们打开内存空间时,我们需要一个连续的内存区域。此时,我们需要一个2M的内存区域,并且有两个1M无用。这导致我们拥有如此多的内存,但我们无法使用它。

复制算法

复制算法(Copying)从标记清除算法演化而来,解决了标记清除算法的内存碎片问题。它将可用内存划分为两个大小相等的块,一次只使用其中一个。当该块的内存用完时,将幸存的对象复制到另一块,然后清理一次使用过的内存空间。它确保了内存的连续可用性,并且在分配内存时不需要考虑诸如内存碎片之类的复杂情况。逻辑清晰,操作高效。

上面的图片非常清楚,很明显已经暴露出另一个问题。与我的140平大三居室一起,它只能用作70平方米的小型两居室房子。价格太高了。

标记算法

Mark-Compact标记过程仍然与标记---清除算法相同,但后续步骤不直接清理可循环使用的对象,而是将所有幸存的对象移动到一端,然后清除结束边界。记忆区。

一方面,标记算法在标记清除算法上升级,以解决内存碎片问题,并且还避免了复制算法只能使用一半内存区域的缺点。它看起来不错,但是从上图中可以看出,它更频繁地更改内存,并且需要对所有幸存对象的引用地址进行排序,这比复制算法要糟糕得多。

世代收集算法(GenerationalCollection)不是严格意义或理论,而是上述三种基本算法思想的组合,以及针对不同情况的一组组合拳。对象生命周期的差异将内存分成几个部分。一般来说,Java堆分为新一代和老一代,因此可以根据每个时代的特点采用最合适的收集算法。在新一代中,每次垃圾收集发现大量对象死亡,只有少量生存,那么使用复制算法,只需要支付少量幸存对象复制成本即可完成收集。在过去,由于物体存活率很高并且没有额外的空间来分配它,它必须通过使用标记清洁或标记来回收---------所以,另一个问题来了,什么块的种类是划分的内存区域,每个块的特殊算法是什么?

记忆模型和回收策略

Java堆(JavaHeap)是JVM管理的最大内存块。堆是垃圾收集器管理的主要区域。这里我们主要分析Java堆的结构。

Java堆主要分为两个领域 - 年轻一代和老年人。年轻一代分为伊甸园区和幸存者区。幸存者区域分为From和To2区域。也许在这个时候每个人都会有疑问,你为什么需要幸存者区,为什么幸存者必须分成两个区域。别担心,我们从头到尾看它,看看对象是如何产生的,以及它为什么没有。

伊甸园区

小发猫的专业研究表明,近98%的对象都在死亡,因此在这种情况下,在大多数情况下,当Eden区域内没有足够的空间用于分发时,对象将被分配到新一代的Eden区域。机器启动MinorGC,这比MajorGC更频繁,并且更快地回收。

在MinorGC之后,伊甸园将被清空,伊甸园地区的大部分物品将被回收,而那些不需要回收的物品将进入幸存者的From区域(如果From区域不够,则直接进入旧区)。

幸存者区

幸存者区域相当于伊甸园和旧区域的缓冲区,类似于交通信号灯中的黄灯。幸存者分为两个区域,一个是From区域,另一个是To区域。每次执行MinorGC时,Eden区域和From幸存对象都被放置在Survivor的To区域中(如果To区域不够,它将直接进入Old区域)。

你为什么需要它?

难道不是老一辈的新一代,直接去老,这是如此的好,它是如此复杂。如果您没有幸存者区域,那么每次您在伊甸园区域进行MinorGC时,幸存的物体将被送到老年人,并且很快就会被填满。有很多对象,虽然MinorGC还没有被淘汰,但它不会持续很长时间。也许第二次,第三次需要清除。在这个时候,进入老年显然不是一个明智的决定。

因此,幸存者的存在是减少发送到老年的对象数量,从而减少MajorGC的发生。幸存者的预筛选保证只有那些在新一代MinorGC中幸存16次的受试者才会被送到老年。

你为什么需要两个?

设置两个Survivor区域的最大好处是解决内存碎片问题。

让我们首先假设如果只有一个区域,幸存者会发生什么。执行MinorGC后,Eden区域被清空,幸存的物体被放置在Survivor区域,并且可能需要清除前一个Survivor区域中的一些物体。问题即将来临,我们如何在此时清除它们?在这种情况下,我们只能标记清理,并且我们知道标记的最大问题是内存碎片。在经常死亡的新一代中,使用标记将不可避免地导致严重的内存碎片。因为幸存者有2个区域,所以MinorGC每次将之前的Eden和From区域中的幸存物体复制到To区域。在第二个MinorGC中,From和To职责被赎回。此时,Eden和To区域中的幸存对象将再次复制到From区域。

这种机制的最大优点是在整个过程中总有一个Survivorspace为空,而另一个非空的Survivorspace是无碎片的。那么,为什么幸存者不会分割更多的碎片呢?例如,如果将幸存者区域细分,则每个部分的空间将很小,这将很容易导致幸存者区域。两个幸存者区域可能是称重后的最佳解决方案。

旧区

老年人占据堆内存空间的2/3,只有在MajorGC期间才会被清理,每次GC都会触发“Stop-The-World”。内存越大,STW越长,因此内存不仅更大,而且更好。由于复制算法在对象存活率高的时候执行了多次复制操作,效率很低,所以这里的老年人就是标记---整理算法。

除此之外,在内存保证机制下,无法放置的对象将直接进入老年,以下情况将进入老年。

大对象

大对象是指需要大量连续内存空间的对象。无论是否“死亡”,这部分目标将直接进入老年。这主要是为了避免Eden区域和两个Survivor区域之间的大量内存复制。当你的系统有许多“死在死里”的大物时,你必须要注意。

长期存活的物体

虚拟机为每个对象定义对象年龄(Age)计数器。在正常情况下,物体将继续在幸存者的From和To区域之间移动。对于幸存者区域中的每个MinorGC,该主题将增加1年。当年龄增加到15岁时,它将转移到老年。当然,15,JVM在这里也支持特殊设置。

动态对象年龄

虚拟机没有注意对象年龄必须是15岁的要求,并且将被放入老年。如果幸存者空间中同一年龄的所有物体的总大小大于幸存者空间的一半,则年龄大于或等于此年龄的物体可以直接进入老年。区,无需等待你“成人”。

这实际上有点像负载平衡。轮询是一种负载平衡,确保每台机器获得相同的请求。它似乎很平衡,但每台机器的硬件都不合理,而且健康也不同。我们还可以根据每台机器收到的请求数或每台机器的响应时间来调整负载均衡算法。

作者简介:聂小龙(花名:率鸽),阿里巴巴高级开发工程师。本文由作者提交,版权归作者所有。

【END】