Java 内存缓存
Java memory cache
是否有可能在内存中实现缓存以避免全堆消耗?
我的 spring-boot java 应用程序使用内存中的缓存,并将过期策略设置为 1 小时(Caffeine 库用于缓存目的)。在那之后所有的缓存实例都在老年代并且需要一个完整的 GC 来收集。现在将 XMX 设置为 10GB,经过几个小时的测试,我可以看到我的缓存包含大约 10 万个实例,但在堆中(恰好在老年代)我可以找到几百万个缓存对象实例。有没有可能在内存中使用缓存来避免这种情况?
您描述的问题是调用内存泄漏。
是的,你可以,但这取决于你使用的 GC 版本。
例如在 G1 中不应该出现这个问题。
所以,如果这是可能的,我建议你切换到 G1。
XpauseTarget 这个标志是为了避免在你的系统中长时间暂停,所以你可以将你的堆清理分成两部分。
您还可以自定义需要 运行 GC 的优先级。 -XX:InitiatingHeapOccupancyPercent=45
如您所见,缓存和分代收集器有着相反的目标。更现代的收集器,如 G1 和 Shenandoah,是基于区域的,这可以让他们更好地处理老一代收集。在 Shenandoah 技术讲座中,您经常会听到他们的开发人员讨论将 LRU 缓存作为压力测试。如果您的 GC 调整得当,这可能不是问题。
您可以将缓存数据结构保留在堆上,但将其条目移开。这可以通过以访问开销为代价将值序列化为 ByteBuffer
来完成。 Apache Mnemonic 提供了另一种方法,它在堆外存储对象字段并透明地编组数据。这避免了序列化成本,但对对象模型具有侵入性。
有像Oak and caches like OHC这样的完全堆外哈希表。它们尽可能多地移动到 GC 之外,但与堆上缓存相比,开销要大得多。这与使用远程缓存(如 memcached 或 redis)相当,因此可能更受欢迎。例如,Memcached 使用 slab 分配来非常有效地处理内存流失。
大多数情况下,您会看到一个小的堆上缓存用于快速本地访问最常用的数据,这些数据由一个用于其他所有内容的大型远程缓存支持。如果您确实需要一个多 GB 的进程内缓存,那么可能需要堆外缓存,或者您可能必须调整 GC 以适应此工作负载。
如果未设置过期时间,缓存中的对象将始终存在。你可以做的是调整 JVM 来避免这种情况,即,如果你正在使用 CMS,-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly
,在设置了这两个选项的情况下,当老年代超过 75% 时,JVM 将被迫执行完整的 gc。
是否有可能在内存中实现缓存以避免全堆消耗?
我的 spring-boot java 应用程序使用内存中的缓存,并将过期策略设置为 1 小时(Caffeine 库用于缓存目的)。在那之后所有的缓存实例都在老年代并且需要一个完整的 GC 来收集。现在将 XMX 设置为 10GB,经过几个小时的测试,我可以看到我的缓存包含大约 10 万个实例,但在堆中(恰好在老年代)我可以找到几百万个缓存对象实例。有没有可能在内存中使用缓存来避免这种情况?
您描述的问题是调用内存泄漏。
是的,你可以,但这取决于你使用的 GC 版本。 例如在 G1 中不应该出现这个问题。 所以,如果这是可能的,我建议你切换到 G1。
XpauseTarget 这个标志是为了避免在你的系统中长时间暂停,所以你可以将你的堆清理分成两部分。
您还可以自定义需要 运行 GC 的优先级。 -XX:InitiatingHeapOccupancyPercent=45
如您所见,缓存和分代收集器有着相反的目标。更现代的收集器,如 G1 和 Shenandoah,是基于区域的,这可以让他们更好地处理老一代收集。在 Shenandoah 技术讲座中,您经常会听到他们的开发人员讨论将 LRU 缓存作为压力测试。如果您的 GC 调整得当,这可能不是问题。
您可以将缓存数据结构保留在堆上,但将其条目移开。这可以通过以访问开销为代价将值序列化为 ByteBuffer
来完成。 Apache Mnemonic 提供了另一种方法,它在堆外存储对象字段并透明地编组数据。这避免了序列化成本,但对对象模型具有侵入性。
有像Oak and caches like OHC这样的完全堆外哈希表。它们尽可能多地移动到 GC 之外,但与堆上缓存相比,开销要大得多。这与使用远程缓存(如 memcached 或 redis)相当,因此可能更受欢迎。例如,Memcached 使用 slab 分配来非常有效地处理内存流失。
大多数情况下,您会看到一个小的堆上缓存用于快速本地访问最常用的数据,这些数据由一个用于其他所有内容的大型远程缓存支持。如果您确实需要一个多 GB 的进程内缓存,那么可能需要堆外缓存,或者您可能必须调整 GC 以适应此工作负载。
如果未设置过期时间,缓存中的对象将始终存在。你可以做的是调整 JVM 来避免这种情况,即,如果你正在使用 CMS,-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly
,在设置了这两个选项的情况下,当老年代超过 75% 时,JVM 将被迫执行完整的 gc。