请问上述语句中的“频繁”和“不太频繁”指的是什么?我们是否在谈论微秒、毫秒、分钟还是天数?年轻代收集相对频繁,因为年轻代空间通常很小,并且可能包含许多不再引用的对象,所以这是有效和快速的。
经过一定数量的年轻代收集后幸存下来的对象最终会被提升或晋升到老年代。请参见图1。老年代通常比年轻代大,其占用率增长较慢。因此,老年代收集不太频繁,但完成时间更长。
请问上述语句中的“频繁”和“不太频繁”指的是什么?我们是否在谈论微秒、毫秒、分钟还是天数?年轻代收集相对频繁,因为年轻代空间通常很小,并且可能包含许多不再引用的对象,所以这是有效和快速的。
经过一定数量的年轻代收集后幸存下来的对象最终会被提升或晋升到老年代。请参见图1。老年代通常比年轻代大,其占用率增长较慢。因此,老年代收集不太频繁,但完成时间更长。
无法给出明确的答案,这主要取决于许多因素,包括平台(JVM版本、设置等)、应用程序和工作负载。
一方面,一个应用程序可能永远不会触发垃圾收集器。它可能只是闲置在那里,或者在 JVM 初始化和应用程序启动后未创建任何对象的情况下执行极长的计算。
另一方面,理论上可能会在几纳秒内结束一个垃圾收集并启动另一个垃圾收集。例如,如果你的应用程序处于堆满的最后阶段,或者正在分配病态大数组,则可能会发生这种情况。
所以:
我们是指微秒、毫秒、分钟还是天?
有可能以上都有,但前两个肯定会在实践中引起问题。
一个表现良好的应用程序不应该太频繁地运行GC。如果你的应用程序触发年轻代垃圾回收超过一两次每秒,那么这可能会导致性能问题。而太频繁的“full”垃圾回收则更糟糕,因为它们的影响更大。然而,一个设计/实现不良的应用程序肯定可能会表现得像这样。
还有一个问题是,GC 运行之间的时间间隔并不总是有意义的。例如,某些 HotSpot GC 实际上具有与正常应用程序线程并发运行的 GC 线程。如果你有足够的核心、足够的RAM和足够的内存总线带宽,那么一个不断运行的并发 GC 可能不会明显影响应用程序性能。
术语说明:
这是一个相对的术语。年轻代集合可能会多达数秒甚至几小时。老年代集合可能每隔几秒到每天进行一次。在大多数系统中,您应该期望有比老年代集合更多的年轻代集合。
很不可能持续很多天。如果GC发生得太频繁,例如<< 100毫秒之间,您可能会遇到OutOfMemoryError: GC Overhead Exceeded
,因为JVM会防止这种情况发生。
TL;DL:「频繁」和「不频繁」是相对的术语,取决于内存分配速度和堆大小。如果您想得到精确的答案,需要针对您特定的应用程序自行进行测量。
假设您的应用程序有两种模式,模式1分配内存并进行计算,而模式2处于空闲状态。
如果模式1的分配比可用堆要小,则不需要进行垃圾回收,直到它完成为止。也许它使用了如此少的RAM,以至于可以进行第二轮模式1而无需进行回收。但是,最终您会耗尽免费堆,jvm将执行「不频繁」的收集。
然而,如果模式1的分配占年轻代堆的重要部分或更大,则会更「频繁」地进行收集。在年轻代收集期间,存活下来的分配(想象数据需要整个模式1操作),将被晋升为老年代,从而给年轻代腾出更多空间。现在可以继续进行年轻代分配和收集。最终老年代堆将耗尽,并必须进行收集,从而变得「不频繁」。
那么,频繁是多频繁呢?这取决于分配率和堆大小。如果 JVM 经常遇到堆限制,它会经常进行垃圾回收。如果有足够的堆(比如100GB),那么 JVM 就不需要长时间进行回收。缺点是,当它最终进行回收时,可能需要很长时间才能释放100GB,使 JVM 停止运行数秒钟(或数分钟!)。当前的 JVM 比这更智能,会偶尔强制进行回收(最好是在模式2下)。而且使用并行收集器,如果必要,可以一直进行回收。就目前而言,“频繁”和“不频繁”这些术语是相对的。实际上,时间并不是固定的,它取决于所涉及的系统。它取决于许多因素,例如:
如果您的应用程序需要大量内存,垃圾回收器会像拼命逃生一样运行。如果您的应用程序不需要太多内存,则垃圾回收器会根据内存使用情况决定运行间隔。
由于规范中说“相对频繁”和不频繁(关于年轻代),我们无法用微秒、毫秒、分钟或天等绝对单位来估计频率。