垃圾收集 (GC) 在 Java 的内存管理中起着重要作用。它有助于回收不再使用的内存。垃圾收集器使用自己的一组线程来回收内存。这些线程称为 GC 线程。有时 JVM 可能会出现 GC 线程过多或过少的情况。在本文中,我们将讨论为什么 JVM 会出现 GC 线程过多/过少的情况、其后果以及解决这些问题的潜在解决方案。
如何查找应用程序的 gc 线程数
您可以通过执行如下所述的线程转储分析来确定应用程序的 GC 线程数:
- 从生产服务器捕获线程转储。
- 使用线程转储分析工具分析转储。
如何设置 gc 线程数
您可以通过设置以下两个 JVM 参数来手动调整 GC 线程的数量:
-XX:ParallelGCThreads=n
:设置垃圾收集器并行阶段使用的线程数-XX:ConcGCThreads=n
:控制垃圾收集器并发阶段使用的线程数
默认 gc 线程数是多少?
如果您没有使用上述两个 JVM 参数明确设置 GC 线程数,则默认 GC 线程数是根据服务器/容器中的 CPU 数量得出的。
–XX:ParallelGCThreads Default
:在 Linux/x86 机器上,它根据以下公式得出:
if (num of processors <=8) {
return num of processors;
} else {
return 8+(num of processors-8)*(5/8);
}
因此,如果您的 JVM 在具有 32 个处理器的服务器上运行,则该ParallelGCThread
值将为:23(即 8 +(32 - 8)*(5/8))。
-XX:ConcGCThreads Default
:它是根据以下公式得出的:
max((ParallelGCThreads+2)/4, 1)
因此,如果您的 JVM 在具有 32 个处理器的服务器上运行,那么:
ParallelGCThread
值为:23(即 8 + (32 - 8)*(5/8))。ConcGCThreads
值为:6(即最大值(25/4, 1)。
JVM 是否会出现过多的 gc 线程?
您的 JVM 可能会无意中拥有过多的 GC 线程,而您通常对此毫不知情。发生这种情况通常是因为默认的 GC 线程数是根据服务器或容器中的 CPU 数量自动确定的。
例如,在具有 128 个 CPU 的机器上,JVM 可能会为垃圾收集的并行阶段分配大约 80 个线程,为并发阶段分配大约 20 个线程,总共大约有 100 个 GC 线程。
如果您在这台 128 CPU 的机器上运行多个 JVM,则每个 JVM 最终可能会有大约 100 个 GC 线程。这可能会导致资源使用量过大,因为所有这些线程都在争夺相同的 CPU 资源。这个问题在容器化环境中尤其明显,因为多个应用程序共享相同的 CPU 核心。这将导致 JVM 分配比必要更多的 GC 线程,从而降低整体性能。
为什么 gc 线程过多会带来问题?
虽然 GC 线程对于高效的内存管理至关重要,但过多的 GC 线程可能会给 Java 应用程序带来严重的性能挑战。
- 上下文切换增加: 当 GC 线程数过高时,操作系统必须频繁在这些线程之间切换。这会导致上下文切换开销增加,其中更多的 CPU 周期用于管理线程而不是执行应用程序的代码。因此,您的应用程序可能会显著变慢。
- CPU 开销: 每个 GC 线程都会消耗 CPU 资源。如果同时处于活动状态的线程过多,它们就会争夺 CPU 时间,从而导致可用于应用程序主要任务的处理能力减少。这种竞争会降低应用程序的性能,尤其是在 CPU 资源有限的环境中。
- 内存争用: GC 线程数量过多,可能会加剧对内存资源的争用。多个线程同时尝试访问和修改内存可能会导致锁争用,从而进一步降低应用程序速度并导致性能瓶颈。
- GC 暂停时间增加,吞吐量降低: 当过多的 GC 线程处于活动状态时,垃圾回收过程的效率会降低,从而导致 GC 暂停时间变长(应用程序会暂时停止)。这些长时间的暂停可能会导致应用程序出现明显的延迟或卡顿。此外,由于在垃圾回收上花费的时间比处理请求的时间多,应用程序的整体吞吐量可能会降低,每秒处理的事务或请求更少,并影响其在负载下的扩展和执行能力。
- 延迟增加: 线程数量过多导致 GC 活动增加,从而导致响应用户请求或处理任务的延迟增加。这对于需要低延迟的应用程序(例如实时系统或高频交易平台)尤其成问题,因为即使是轻微的延迟也可能造成严重后果。
- 收益递减: 超过某个点后,添加更多 GC 线程不会提高性能。相反,它会导致收益递减,即管理这些线程的开销超过了更快垃圾收集带来的好处。这可能会导致应用程序性能下降,而不是达到预期的优化效果。
为什么 GC 线程太少会带来问题?
虽然 GC 线程过多可能会造成性能问题,但 GC 线程过少也会给 Java 应用程序带来同样的问题。原因如下:
- 垃圾收集时间更长: GC 线程越少,垃圾收集过程可能需要更长的时间才能完成。由于可用于处理工作负载的线程越少,回收内存所需的时间就越长,从而导致 GC 暂停时间延长。
- 应用程序延迟增加: 垃圾收集时间越长,延迟就越大,尤其是对于需要低延迟操作的应用程序。用户可能会遇到延迟,因为应用程序在等待垃圾收集完成时会变得无响应。
- 吞吐量降低: GC 线程数量减少意味着垃圾收集器无法高效工作,从而导致整体吞吐量降低。您的应用程序每秒处理的请求或交易可能会减少,从而影响其在负载下扩展的能力。
- CPU 利用率低: 如果 GC 线程太少,则垃圾收集期间 CPU 核心可能无法充分利用。这会导致可用资源的利用率低下,因为某些核心处于空闲状态,而其他核心则负担过重。
OutOfMemoryErrors
内存泄漏风险增加:如果垃圾收集器由于线程太少而无法跟上内存分配的速度,则可能无法足够快地回收内存。这会增加应用程序内存不足的风险,从而导致OutOfMemoryErrors
应用程序崩溃。此外,GC 线程不足会减慢垃圾收集过程,从而加剧内存泄漏,使更多未使用的对象堆积在内存中。随着时间的推移,这可能会导致内存使用量过大,并进一步降低应用程序性能。
优化 GC 线程数的解决方案
如果您的应用程序由于 GC 线程数量过多或不足而出现性能问题,请考虑使用上述 JVM 参数手动设置 GC 线程数:
-XX:ParallelGCThreads=n
-XX:ConcGCThreads=n
在生产中进行这些更改之前,研究应用程序的 GC 行为至关重要。首先使用工具收集和分析 GC 日志。此分析将帮助您确定当前线程数是否导致性能瓶颈。基于这些见解,您可以对 GC 线程数进行明智的调整,而不会引入新问题
- 注意:在将更改投入生产之前,请务必先在受控环境中测试更改,以确认它们能够提高性能。