JVM 《二》垃圾收集算法 及 介绍

垃圾收集

Java HotSpot VM内存系统的世代特性提供了灵活性,可以使用适合各种应用程序需求的特定垃圾收集算法。Java HotSpot VM支持多种不同的垃圾收集算法,旨在满足不同的暂停时间和吞吐量要求。

背景

程序员的Java编程语言的一个主要吸引力是它是第一个提供内置自动内存管理或垃圾收集(GC)的主流编程语言。在传统语言中,使用显式分配/免费模型分配动态内存。实际上,这不仅是用传统语言编写的程序中的内存泄漏,程序错误和崩溃的主要来源,而且还是性能瓶颈和模块化,可重用代码的主要障碍。(如果模块之间没有明确且难以理解的合作,则确定跨模块边界的自由点几乎是不可能的。)在Java编程语言中,垃圾收集也是支持安全模型所需的“安全”执行语义的重要部分。

垃圾收集器通过仅在可以证明正在运行的程序不再可访问该对象的情况下回收对象,自动处理在后台释放未使用的对象内存。这个过程的自动化不仅完全消除了由于释放太少而导致的内存泄漏,而且还消除了程序崩溃和由于过多释放导致的难以找到的参考错误。

传统上,相对于无显式模型,垃圾收集被认为是阻碍性能的低效过程。实际上,使用现代垃圾收集技术,性能已经提高了很多,整体性能实际上明显优于显式释放对象所提供的性能。

Java HotSpot垃圾收集器

除了包括下面描述的最先进的功能外,内存系统还被设计为一个干净的,面向对象的框架,可以轻松地进行检测,试验或扩展以使用新的垃圾收集算法。

Java HotSpot垃圾收集器的主要功能如下所示。总的来说,这些功能非常适合于需要最高性能的应用程序,以及长期运行的应用程序,其中由于碎片导致的内存泄漏和内存不可访问性是非常不合需要的。

准确性

Java HotSpot垃圾收集器是一个完全准确的收集器。相比之下,许多其他垃圾收集器是保守的或部分准确的。虽然保守的垃圾收集很有吸引力,因为它很容易添加到没有垃圾收集支持的系统,但它有一些缺点。通常,保守的垃圾收集器容易出现内存泄漏,不允许对象迁移,并且可能导致堆碎片。

保守的收集器不确定所有对象引用的位置。因此,假设看起来引用对象的内存字实际上是对象引用,它必须是保守的。这意味着它可以产生某些类型的错误,例如混淆对象指针的整数。看起来像指针的内存单元被视为指针 - 而GC变得不准确。这有几个负面影响。首先,当出现这样的错误时(实际上并不常见),内存泄漏可能以应用程序员无法再现或调试的方式无法预测地发生。其次,由于它可能犯了一个错误,保守派收集器必须使用句柄间接引用对象 - 降低性能 - 或避免重新定位对象,因为重定位无柄对象需要更新对象的所有引用。如果收集器不确定明显的引用是否是真正的引用,则无法完成此操作。无法重定位对象会导致对象内存碎片,更重要的是,会阻止使用下面描述的高级世代复制收集算法。

因为Java HotSpot收集器是完全准确的,所以它可以提供一些保守的收集器无法做出的强有力的设计保证:

可以可靠地回收所有不可访问的对象存储器。

可以重新定位所有对象,允许对象内存压缩,从而消除对象内存碎片并增加内存局部性。

准确的垃圾收集机制可避免意外的内存泄漏,实现对象迁移,并提供完整的堆压缩。Java Hotspot VM中的GC机制可以很好地扩展到非常大的堆。

分代复制集合

Java HotSpot VM采用了最先进的世代复制收集器,它提供了两个主要的好处:

与非代收集器相比,大多数程序的分配速度和整体垃圾收集效率都有所提高

用户可感知的垃圾收集暂停的频率和持续时间的相应减少

分代收集器利用了这样的事实:在大多数程序中,绝大多数对象(通常大于95%)都是非常短暂的(例如,它们被用作临时数据结构)。通过将新创建的对象隔离到对象托儿所中,分代收集器可以完成多项任务。首先,因为新对象在对象托儿所中以类似堆栈的方式连续分配,所以分配变得非常快,因为它仅涉及更新单个指针并对托儿所溢出执行单个检查。其次,当托儿所溢出时,托儿所中的大多数对象已经死亡,允许垃圾收集器简单地将少数幸存的对象移动到其他地方,并避免对托儿所中的死对象进行任何回收工作。

并行年轻代收集器

上述单线程复制收集器虽然适用于许多部署,但可能成为在应用程序中进行扩展的瓶颈,否则该应用程序将被并行化以利用多个处理器。为了充分利用多处理器计算机上的所有可用CPU,Java HotSpot VM为年轻代提供了一个可选的多线程收集器,其中活动对象的跟踪和复制由多个并行工作的线程完成。该实现已经过精心调整,以平衡所有可用处理器之间的收集工作,使收集器可以扩展到大量处理器。这减少了收集年轻空间的暂停时间并最大化垃圾收集吞吐量。并行收集器已经过测试,系统包含超过100个CPU和0。5 TB的堆。并行年轻代收集器是与服务器VM一起使用的默认垃圾收集算法。

移动对象时,并行收集器会尝试将相关对象保持在一起,从而提高内存局部性和缓存利用率,从而提高mutator的性能。这是通过以深度优先复制对象来完成的 。

并行收集器还可以更加优化地使用可用内存。它不需要保留旧对象空间的一部分来保证复制所有活动对象的空间。相反,它使用一种新颖的技术来推测性地尝试复制对象。如果旧的对象空间很少,这种技术允许收集器平滑地切换到压缩堆而无需保留任何空间。这样可以更好地利用可用的堆空间。

最后,并行收集器能够动态调整其可调参数以响应应用程序的堆分配行为,从而提高了在各种应用程序和环境中的垃圾收集性能。这意味着减少客户的手动调整工作。此功能最初是与并行收集器一起引入的,现在可用于许多其他垃圾收集算法。

与默认的单线程收集器相比,并行收集器的收支平衡点似乎介于两到四个CPU之间,具体取决于平台和应用程序。预计未来版本将进一步改进。

Mark-Compact旧物件收集器

虽然世代复制收集器有效地收集大多数死对象,但是较长寿命的对象仍然在旧对象存储区域中累积。有时,基于低内存条件或编程请求,必须执行旧的对象垃圾回收。默认情况下,Java HotSpot VM使用标准的mark-compact集合算法,该算法从其根目录遍历活动对象的整个图形 ,然后扫描内存,压缩死对象留下的间隙。通过压缩堆中的间隙,而不是将它们收集到空闲列表中,消除了内存碎片,并通过消除空闲列表搜索来简化旧对象分配。

主要是Concurrent Mark-Sweep Collector

对于需要大堆的应用程序,由默认的旧代mark-compact收集器引起的收集暂停通常会导致中断,因为应用程序线程暂停的时间与堆的大小成比例。Java HotSpot VM为旧对象空间实现了可选的并发收集器,可以利用备用处理器周期(或备用处理器)来收集大堆,同时暂停应用程序线程非常短的时间。这是通过在应用程序线程执行时执行大量跟踪和清理工作来完成的。在某些情况下,峰值应用程序吞吐量可能会略有下降,因为某些处理器周期用于并发收集活动; 然而,

并行老一代收集器

当前版本的Java HotSpot VM为旧一代引入了并行标记 - 紧凑收集器,旨在提高具有大堆的应用程序的可伸缩性。并发标记扫描收集器侧重于减少暂停时间,并行旧收集器侧重于通过在停止世界暂停期间同时使用多个线程来收集旧代来增加吞吐量。并行旧收集器在内部使用许多新颖的技术和数据结构来实现高可伸缩性,同时保留精确垃圾收集的好处并且在收集周期期间具有最小的簿记开销。

欲获得更多信息

有关Java HotSpot VM中支持的垃圾收集算法的更多信息,请参阅 内存管理白皮书。


收集器详细介绍


串行收集器

串行收集器使用单个线程来执行所有垃圾收集工作,这使得它相对有效,因为线程之间没有通信开销。

它最适合单处理器机器,因为它无法利用多处理器硬件,尽管它对于具有小数据集(最大约100 MB)的应用程序的多处理器非常有用。默认情况下,在某些硬件和操作系统配置上选择串行收集器,或者可以使用该选项显式启用串行收集器-XX:+UseSerialGC。

并行收集器

并行收集器也称为吞吐量收集器,它是一个类似于串行收集器的分代收集器。串行和并行收集器之间的主要区别在于并行收集器具有多个线程,用于加速垃圾收集。

并行收集器适用于在多处理器或多线程硬件上运行的中型到大型数据集的应用程序。您可以使用该-XX:+UseParallelGC选项启用它。

并行压缩是一种使并行收集器能够并行执行主要集合的功能。如果没有并行压缩,主要集合将使用单个线程执行,这可能会显着限制可伸缩性。如果-XX:+UseParallelGC已指定选项,则默认启用并行压缩。您可以使用该 -XX:-UseParallelOldGC 选项禁用它。

最常见的收藏家

并发Mark Sweep(CMS)收集器和Garbage-First(G1)垃圾收集器是两个主要并发的收集器。大多数并发收集器同时为应用程序执行一些昂贵的工作。

G1垃圾收集器:此服务器式收集器适用于具有大量内存的多处理器计算机。它以高概率满足垃圾收集暂停时间目标,同时实现高吞吐量。

默认情况下,在某些硬件和操作系统配置上选择G1,或者可以使用明确启用G1 -XX:+UseG1GC。

CMS收集器:此收集器适用于喜欢较短垃圾收集暂停且可以与垃圾收集共享处理器资源的应用程序。

使用该选项-XX:+UseConcMarkSweepGC可启用CMS收集器

从JDK 9开始,不推荐使用CMS收集器。

Z垃圾收集器

Z垃圾收集器(ZGC)是一个可扩展的低延迟垃圾收集器。ZGC同时执行所有昂贵的工作,而不停止执行应用程序线程。

ZGC适用于需要低延迟(小于10毫秒暂停)和/或使用非常大的堆(多兆兆字节)的应用程序。您可以使用该-XX:+UseZGC选项启用。

从JDK 11开始,ZGC作为实验性功能提供。

选择收集器

除非您的应用程序具有相当严格的暂停时间要求,否则首先运行您的应用程序并允许VM选择收集器。

如有必要,请调整堆大小以提高性能。如果性能仍然无法达到您的目标,请使用以下指南作为选择收集器的起点:

如果应用程序具有较小的数据集(最大约100 MB),则选择带有该选项的串行收集器-XX:+UseSerialGC。

如果应用程序将在单个处理器上运行且没有暂停时间要求,则选择带有该选项的串行收集器-XX:+UseSerialGC。

如果(a)峰值应用程序性能是第一优先级并且(b)没有暂停时间要求或暂停一秒或更长时间是可接受的,那么让VM选择收集器或选择并行收集器-XX:+UseParallelGC。

如果响应时间比总吞吐量更重要,并且垃圾收集暂停必须保持短于大约一秒钟,那么选择一个大多数并发收集器使用-XX:+UseG1GC或-XX:+UseConcMarkSweepGC。

如果响应时间是高优先级,和/或您使用的是非常大的堆,则选择完全并发的收集器-XX:UseZGC。

这些指南仅提供了选择收集器的起点,因为性能取决于堆的大小,应用程序维护的实时数据量以及可用处理器的数量和速度。

如果推荐的收集器未达到所需性能,则首先尝试调整堆和生成大小以满足所需目标。如果性能仍然不足,那么尝试使用不同的收集器:使用并发收集器来减少暂停时间,并使用并行收集器来提高多处理器硬件的总吞吐量。


专业GC设置:


并行收集器

并行收集器(这里也称为吞吐量收集器)是类似于串行收集器的分代收集器。串行和并行收集器之间的主要区别在于并行收集器具有多个线程,用于加速垃圾收集。

使用命令行选项启用并行收集器-XX:+UseParallelGC。默认情况下,使用此选项,可以并行运行次要和主要集合,以进一步减少垃圾收集开销。


并发标记扫描(CMS)收集器

并发标记扫描(CMS)收集器专为需要更短垃圾收集暂停且能够在应用程序运行时与垃圾收集器共享处理器资源的应用程序而设计。

通常,具有相对大的长寿命数据集(大型旧代)并在具有两个或更多处理器的机器上运行的应用程序倾向于从该收集器的使用中受益。使用命令行选项启用CMS收集器-XX:+UseConcMarkSweepGC。

不推荐使用CMS收集器。强烈考虑使用Garbage-First收集器。

G1收集器

垃圾优先垃圾收集器简介

Garbage-First(G1)垃圾收集器的目标是具有大量内存的多处理器机器。它试图以高概率满足垃圾收集暂停时间目标,同时在几乎不需要配置的情况下实现高吞吐量。G1旨在使用当前目标应用程序和环境提供延迟和吞吐量之间的最佳平衡,其功能包括:

堆大小最多为10 GB或更大,超过50%的Java堆占用实时数据。

对象分配和促销的比率随时间变化很大。

堆中存在大量碎片。

可预测的暂停时间目标目标,不超过几百毫秒,避免长时间的垃圾收集暂停。

G1取代了Concurrent Mark-Sweep(CMS)收集器。它也是默认的收集器。

G1收集器实现了高性能,并尝试以下面几节中描述的几种方式实现暂停时间目标。

启用G1

Garbage-First垃圾收集器是默认收集器,因此通常您不必执行任何其他操作。您可以通过 -XX:+UseG1GC在命令行上提供来明确启用它。

基本概念

G1是世代的,增量的,并行的,大多数并发的,停止世界的,疏散的垃圾收集器,它监视每个世界上停顿的暂停时间目标。与其他收藏家类似,G1将堆分成(虚拟)年轻和老一代。空间回收工作集中在年轻一代,这是最有效的,老一代偶尔进行空间回收

有些操作总是在世界各地暂停时执行以提高吞吐量。其他需要花费更多时间停止应用程序的操作,例如全局标记等整堆操作,是与应用程序并行执行的。为了避免空间回收的世界停顿,G1逐步并行地逐步进行空间回收。G1通过跟踪有关先前应用程序行为和垃圾收集暂停的信息来建立相关成本模型,从而实现可预测性。它使用此信息来确定暂停中完成的工作的大小。例如,G1首先在最有效的区域中回收空间(即大多数区域充满垃圾,因此名称)。

G1主要通过使用疏散来回收空间:在选定的存储区域中找到的活动对象被复制到新的存储区域中,在此过程中压缩它们。撤离完成后,活动对象先前占用的空间将被重新用于应用程序的分配。

Garbage-First收集器不是实时收集器。它试图在较长时间内以高概率满足设定的暂停时间目标,但对于给定的暂停并不总是绝对确定。


Z垃圾收集器

Z垃圾收集器(ZGC)是一个可扩展的低延迟垃圾收集器。ZGC同时执行所有昂贵的工作,而不会停止执行应用程序线程超过10毫秒,这使得它适用于需要低延迟和/或使用非常大的堆(多兆兆字节)的应用程序。

Z垃圾收集器可用作实验性功能,并使用命令行选项启用 -XX:+UnlockExperimentalVMOptions -XX:+UseZGC。

设置堆大小

ZGC最重要的调整选项是设置最大堆大小(-Xmx)。由于ZGC是并发收集器,因此必须选择最大堆大小,以便:1)堆可以容纳应用程序的实时集,以及2)堆中有足够的空间允许在GC处理时分配服务运行。需要多少空间取决于应用程序的分配率和实时设置大小。一般来说,你给ZGC的内存越多越好。但与此同时,浪费内存是不可取的,因此所有关于在内存使用和GC运行频率之间找到平衡点。

设置并发GC线程数

可能要查看的第二个调优选项是设置并发GC线程的数量(-XX:ConcGCThreads)。ZGC具有自动选择此编号的启发式方法。这种启发式方法通常运行良好,但根据应用程序的特性,可能需要进行调整。此选项基本上决定了应该给出多少CPU时间。给它太多,GC将从应用程序中窃取过多的CPU时间。给它太少,应用程序可能比GC可以更快地分配垃圾。


G1 GC的人机工程学默认值


选项和默认值 描述

-XX:MaxGCPauseMillis=200

最大暂停时间的目标。

-XX:GCPauseTimeInterval= 

最大暂停时间间隔的目标。默认情况下,G1不设置任何目标,允许G1在极端情况下背靠背执行垃圾收集。

-XX:ParallelGCThreads= 

垃圾收集暂停期间用于并行工作的最大线程数。这是通过以下方式从VM运行的计算机的可用线程数得出的:如果进程可用的CPU线程数小于或等于8,则使用该线程。否则,将五分之八的线程添加到最终线程数。

在每次暂停开始时,使用的最大线程数进一步受最大总堆大小的限制:G1每个 -XX:HeapSizePerGCThreadJava堆容量不会使用多个线程。

-XX:ConcGCThreads=  

用于并发工作的最大线程数。默认情况下,此值-XX:ParallelGCThreads 除以4。

-XX:+G1UseAdaptiveIHOP

-XX:InitiatingHeapOccupancyPercent=45

用于控制启动堆占用的默认值指示该值的自适应确定被打开,并且对于前几个收集周期G1的默认值将使用旧一代的45%的占用作为标记开始阈值。

-XX:G1HeapRegionSize= 

基于初始和最大堆大小的堆区域大小的集合。这个堆包含大约2048个堆区域。堆区域的大小可以在1到32 MB之间变化,并且必须是2的幂。

-XX:G1NewSizePercent=5

-XX:G1MaxNewSizePercent=60

年轻一代的总大小,在这两个值之间变化,作为当前使用的Java堆的百分比。

-XX:G1HeapWastePercent=5

集合中允许的未回收空间将候选者设置为百分比。如果集合集合中的可用空间低于该空间,则G1将停止空间回收阶段。

-XX:G1MixedGCCountTarget=8

许多集合中空间回收阶段的预期长度。

-XX:G1MixedGCLiveThresholdPercent=85

在此空间回收阶段,不会收集活动对象占用率高于此百分比的旧代区域。


超快线程同步

Java编程语言允许使用多个并发的程序执行路径 - 线程。Java编程语言提供语言级线程同步,这使得使用细粒度锁定表达多线程程序变得容易。与Java编程语言中的其他微操作相比,以前的同步实现(例如Classic VM中的同步实现)效率非常低,使得细粒度同步成为主要的性能瓶颈。

Java HotSpot VM采用了前沿技术,可实现无竞争和同步操作,从而大大提高了同步性能。动态构成大多数同步的无争用同步操作是采用超快速,恒定时间的技术实现的。通过最新的优化,在最好的情况下,即使在多处理器计算机上,这些操作也基本上是免费的。即使对于具有大量锁争用的应用程序,竞争同步操作也使用高级自适应旋转技术来提高吞吐量。结果,同步性能变得如此之快,以至于绝大多数真实世界的程序都不是一个重要的性能问题。

64位架构

早期版本的Java HotSpot VM仅限于处理4 GB的内存 - 即使是在64位操作系统(如Solaris OE)上也是如此。虽然桌面系统有4千兆字节,但现代服务器可以包含更多内存。例如,Sun Fire E25K服务器每个域最多支持1.15 TB的内存。使用64位JVM,基于Java技术的应用程序现在可以利用此类系统的完整内存。

有几类应用程序使用64位寻址可能很有用。例如,那些在内存中存储非常大的数据集的那些。应用程序现在可以避免从磁盘分页数据或从RDBMS中提取数据的开销。这可以在这种类型的应用中导致显着的性能改进。

Java HotSpot VM现在是64位安全的,Server VM包括对32位和64位操作的支持。用户可以通过使用命令行标志选择32位或64位操作 -d32或 -d64分别。Java Native Interface的用户需要重新编译他们的代码才能在64位VM上运行它。

对象包装

添加了对象打包功能,以最大限度地减少不同大小的数据类型之间的浪费空间。这主要是64位环境中的一项优势,但即使在32位VM中也具有很小的优势。

例如:

public class Button {

char shape;

String label;

int xposition;

int yposition;

char color;

int joe;

object mike;

char armed;

}


这会浪费: color和 joe(三个字节填充到 int边界) joe和 mike(64位VM上的四个字节填充到指针边界)之间的空间。现在,字段被重新排序为如下所示:

...

object mike;

int joe;

char color;

char armed;

...


在此示例中,不会浪费任何内存空间。


文章借阅 :https://www.oracle.com/technetwork/java/whitepaper-135217.html#memory

https://docs.oracle.com/en/java/javase/11/gctuning/available-collectors.html#GUID-F215A508-9E58-40B4-90A5-74E29BF3BD3C

https://docs.oracle.com/en/java/javase/11/gctuning/garbage-first-garbage-collector.html#GUID-082C967F-2DAC-4B59-8A81-0CEC6EEB9016

持续更新 多多交流

---------------------

作者:a_Ygygs_Dxdsr_XdMss

来源:CSDN

原文:https://blog.csdn.net/weixin_42749765/article/details/87350721

版权声明:本文为博主原创文章,转载请附上博文链接!

你可能感兴趣的:(JVM 《二》垃圾收集算法 及 介绍)