作者:享学James老师
都说JVM是大牛们玩的技术,其实未必,如果面试官和你谈到Java内存管理,那么首先,我建议你首先要 了解Java垃圾收集的工作原理。 因为经常在运行JAVA应用程序时,大多数开发者是使用JVM自动帮你管理GC垃圾回收器(完全不关注,JVM自动完成回收),码农们只关注业务代码实现,不需要关注JVM是怎么管理的,对大家而言,更多人只知道程序正在运行中。但是老铁们,当你写的JAVA程序开始面临性能下降时, 码农与架构师的区别就来了,所有的性能问题其实归根到底就是我们的GC回收效率变低了。
因此,让我们首先了解什么是JVM GC模型,然后,我们可以看到 如何控制它并分析GC日志以查找应用程序中发生的任何差异。
自动垃圾收集是指对堆内存的查看,并识别哪些对象正在使用哪些对象,以及删除未使用的对象的过程。
首先我们看一下自动GC垃圾收集, 它的步骤如下:
该过程的第一步称为标记。其实就是垃圾收集器识别哪些内存正在使用,哪些内存不在使用的地方。
如果必须扫描系统中的所有对象,将是一个非常耗时的过程。
正常删除是指移除未引用的对象,留下引用的对象和指向空闲空间的指针。
要进一步提高性能,除了删除未引用的对象外,还可以压缩剩余的引用对象。
通过将引用的对象移动到一起,这使得新的内存分配更加容易和快速。
当正在进行垃圾收集时,如果你的应用程序在该时间段内没有响应时,其实我们的期望是,GC应该花费最少的时间来回收它; 当然, 如果花费很多时间,则证明你的应用GC设置是有问题滴。
我们来看看下面的JVM内存模型,它分为不同的部分。JVM堆内存在物理上分为两部分 - Young Generation(新生代)和Old Generation(老年代)。
1.首先,将所有新的对象都分配给伊Eden space(伊甸园)。两个Survivor Space(幸存者区)都是空的。
2.当Eden space(伊甸园)填满时,会触发一个小的垃圾收集。
3.引用的对象被移动到第一个幸存者空间。清除Eden space(伊甸园)时,将删除未引用的对象。
4.下次要GC回收时,Eden space(伊甸园)空间也会发生同样的事情。删除未引用的对象,并将引用的对象移动到幸存者空间。但是,在这种情况下,它们被移动到第二个幸存者空间(S1)。
5.在较小的GC之后,当老化的对象达到一定的年龄阈值(在该示例中为8)时,它们从新生代晋升到老年代。
最终,将对老一代进行主要的GC回收,清理和压缩该空间。
垃圾收集是指当JVM不再需要对象时,需要将它回收,释放内存。它包括查找不再使用的对象,释放与这些对象关联的内存,并偶尔压缩堆以防止内存碎片。
垃圾收集器使用一个或多个线程来执行回收工作。一般来说,为了完成跟踪对象引用及在内存中移动对象的工作,它需要确保应用程序线程当前没有使用这些对象,如果应用程序线程正在使用对象,GC回收时会导致对象的内存位置发生变化,可能发生不可预测的事情。这就是垃圾收集器在执行某些任务时必须暂停所有应用程序线程的原因。 这些暂停有时被称为Stop-The-World暂停(吊炸天,全世界都被停止,哈哈)。
垃圾收集调优的第一步是 调整堆的大小。 这是因为如果堆太小,则会发生太多的GC回收回收内存次数,这会降低整体应用程序吞吐量。如果堆太大,那么GC回收次数会更少,但GC需要很长的时间,那么你的系统响应时间指标会受到影响。并行收集器特别容易受到堆大小的影响,因此如果你需要大的堆并且暂停时间较短, 那么你应该尝试使用G1GC收集器。
备注: 自从Java 9和Shenandoah垃圾收集器被视为还处于“实验性”阶段,不推荐使用并发标记扫描(CMS)收集器。但如果你正在运行在线交互式应用程序,那么系统会默认选择G1GC收集器,如果你正在运行脱机批处理应用程序,那么并行收集器应该是你的首选,这是我给大家的建议。
堆的大小由两个值控制: 使用ms标志指定的初始值和使用mx标志指定的最大值。
-Xms1g -Xmx8g
堆的初始大小和最大大小,可以由JVM根据工作负载自动调整堆大小。如果JVM遇到内存压力并且观察到GC执行次数过多, 它会不断增加堆,直到内存压力消失为止,或直到堆达到其最大值为止。 如果内存压力很低,JVM还可以通过缩小堆大小来决定减少暂停时间。这个过程称为 自适应大小调整 , 它不仅可以调整堆的整体大小,还可以调整年轻代和老代的大小和比例。
当然,如果你想调整GC行为和大小, 我建议你可以选择关闭自适应大小调整。 这可以节省JVM,这是计算堆大小所需的一小段时间。你可以通过将标志设置UseAdaptiveSizePolicy为false 来执行此操作。
-XX:-UseAdaptiveSizePolicy
此外,将初始堆大小设置为与最大堆大小相同的值,或将初始新生代大小设置为与最大新生代大小相同的值, 这样操作可以有效地关闭自适应大小调整。
一般来说堆大小的最大设置准则就是 最大堆大小不应超过计算机上的物理内存量。 如果你运行多个JVM,则最大堆大小的总和不应超过计算机的物理内存。
在G1GC中,调整参数MaxGCPauseMillis执行以下所有优化, 以尝试实现指定的暂停时间目标:
调整堆的大小
更快开始后台处理
调整要提升为旧一代的对象的期限阈值
调整混合GC循环期间处理的旧区域数
G1GC是一个并发收集器。 这意味着当应用程序线程仍在运行时,垃圾收集进程的某些阶段可以并发运行。并且由于正在运行的应用程序可以继续产生垃圾,我们可能会遇到应用程序耗尽旧代内存而垃圾收集器仍在垃圾收集过程中的情况。也就是说,正在运行的应用程序生成的垃圾比它清理的速度快。 这种情况称为并发模式故障, 具体取决于故障发生的时间。如果您在GC日志中看到很多这些错误; 解决方案是增加堆的大小,更早地启动G1后台处理,或者通过使用更多后台线程来加速GC处理。
要更频繁地执行G1后台活动,您可以降低触发G1循环的阈值。这是通过减少InitiatingHeapOccupancyPercent标志的值来实现的。
-XX:InitiatingHeapOccupancyPercent=45
默认情况下,此标志设置为45。这意味着 当堆填充45%时会触发GC循环。 减少此值意味着GC会更早且更频繁地触发。但应注意的是,该值不会设置为太低而导致GC过于频繁发生的数字。
要增加后台线程数,请使用该ConcGCThreads标志。
-XX:ConcGCThreads=4
此标志的默认值设置为ParallelGCThreads加2 的值除以4.只要计算机上有足够的CPU可用,就可以增加此值而不会导致任何性能损失。
如果调整堆大小并调整收集器对你不起作用, 那么你可以尝试另一个收集器。 如果你仍然没有取得好成绩, 那么你需要考虑调整应用程序代码本身的问题了 , 好了,写了这么多,希望对大家有帮助。
END
欢迎长按下图关注公众号: 享学课堂online!
公众号后台回复 【java】 ,获取精选准备的架构学习资料(视频+文档+架构笔记)