【问题标题】:Is it possible to mark java objects non-collectable from gc perspective to save on gc-sweep time?是否可以从 gc 的角度将 java 对象标记为不可收集以节省 gc 扫描时间?
【发布时间】:2018-10-10 02:34:00
【问题描述】:

是否可以从 gc 的角度将 java 对象标记为不可收集以节省 gc 扫描时间?

类似于http://wwwasd.web.cern.ch/wwwasd/lhc++/Objectivity/V5.2/Java/guide/jgdStorage.fm.html 的东西,特别是非垃圾收集容器(非垃圾收集?)。

问题是我有很多普通的临时对象,但我有更大(几个 Gigs)的对象存储用于缓存目的。 Java GC 不应该无缘无故地遍历所有那些试图找到任何要收集的缓存千兆字节,因为它们包含有自己的超时的缓存数据。

通过这种方式,我可以以自定义方式将我的数据划分为无限生命和正常生命的对象,并且希望 GC 会很快,因为普通对象的生命不会那么长并且数量较少。

有一些解决这个问题的方法,例如Apache DirectMemory 和 Commercial Terracotta BigMemory(http://terracotta.org/products/bigmemory),但是 java-native 解决方案会更好(我的意思是免费的,可能更多可靠的?)。另外我想避免序列化开销,这意味着它应该发生在同一个 jvm 中。据我了解,DirectMemory 和 BigMemory 主要操作堆外,这意味着对象必须在 jvm 外部的内存中序列化/反序列化。简单地在 jvm 中标记非 gc 区域似乎是一个更好的解决方案。使用文件进行缓存也不是一种选择,它具有相同的无法承受的序列化/反序列化开销 - 用例是 HA 服务器,其中大量数据以随机(人工)顺序使用并且需要低延迟。

【问题讨论】:

  • 是什么让您认为 BigMemory 不是 Java 原生的?听起来您真正的意思是免费。
  • 您可以自己使用直接内存来缓存数据(堆外)我这样做是为了保持 100 GB 的堆外空间。
  • @PeterLawrey 自己使用直接内存不是一个选项,因为我想避免序列化开销,这意味着它应该发生在同一个 jvm 中。
  • @MattBall 是的,最好是免费的并且(本机,单个 jvm)没有序列化开销。
  • @MarkoTopolnik 当我知道内存快用完时,也许我可以有一个指示器,以清除一些缓存......嗯......

标签: java garbage-collection


【解决方案1】:

JVM 管理的任何内存也由 JVM 进行垃圾收集。任何无需反序列化即可直接用于 Java 方法的“活动”对象都必须存在于 JVM 内存中。因此,据我了解,您不能拥有不受垃圾收集影响的活动对象。

另一方面,您描述的用法应该使垃圾收集的分代方法非常有效。如果您的大物件在附近停留了一段时间,它们会被检查回收的频率降低。所以我怀疑避免这些检查会带来很多好处。

【讨论】:

  • 只是我的假设,当 jvm 处于饥饿状态时,它也会开始扫描所有不必要的对象。
  • 作为一般规则,您应该假设 GC非常非常聪明。
【解决方案2】:

是否可以从 gc 的角度将 java 对象标记为不可收集以节省 gc 扫描时间?

不,这是不可能的。

您可以通过保持对象可访问性来防止对象被垃圾回收,但 GC 仍需要跟踪它们以检查每个已满时的可访问性; GC(至少)。

只是我的假设,当 jvm 处于饥饿状态时,它也会开始扫描所有不必要的对象。

是的。那是正确的。但是,除非您有很多想要以这种方式处理的对象,否则开销可能微不足道。 (无论如何,一个更好的主意是给 JVM 更多内存……如果可能的话。)

【讨论】:

    【解决方案3】:

    很简单,为了让您能够做到这一点,垃圾收集算法需要知道这样一个标志,并在工作时将其考虑在内。

    我不知道有任何标准 GC 算法具有这样的标志,因此要使其工作,您需要编写自己的 GC 算法(在确定一些可行的方式来传达此信息之后)。

    原则上,事实上,您已经开始走这条路——您正在决定应该如何进行垃圾收集,而不是乐于将其留给 JVM 的 GC 算法。你描述的情况是你可以衡量的问题吗?现有垃圾收集不足但您的计划可行的东西?垃圾收集器经过了非常好的调整,所以如果“低效”的默认策略实际上比您天真的最优策略更快,我不会感到惊讶。

    (在最好的情况下进行手动内存管理很棘手且容易出错;在使用库存垃圾收集器处理其余部分的同时自己管理 一些 内存似乎更糟糕。我希望你会遇到很多边缘情况,GC 假设它“知道”整个堆发生了什么,这将不再是真的。如果可以的话,请避开......)

    【讨论】:

      【解决方案4】:

      推荐的方法是使用商业 RTSJ 实现来避免 GC,或者使用堆外内存。也可以查看缓存的软引用(它们确实被收集)。

      不建议这样做: 如果由于某种原因您认为这些选项不够用,您可以查看不安全的直接内存访问(sun.misc.Unsafe 的一部分)。您可以使用“theUnsafe”字段来获取“Unsafe”实例。 Unsafe 允许通过 'allocateMemory' 和 'freeMemory' 分配/释放内存。这不受 GC 控制,也不受 JVM 堆大小的限制。一旦你走这条路,就不能保证对 GC/应用程序的影响 - 这就是为什么使用字节缓冲区可能是要走的路(如果你没有使用类似 RTSJ 的实现)。

      希望这会有所帮助。

      【讨论】:

        【解决方案5】:

        活动的 Java 对象将始终成为 GC 生命周期的一部分。或者换一种说法,将对象标记为非 gc 与让对象被根引用(例如静态最终映射)引用的开销顺序相同。

        但再想一想,放入缓存中的数据很可能是临时的,最终会被驱逐。那时你会重新开始喜欢 JVM 和 GC。 如果您有 100 GB 的永久数据,您可能需要重新考虑应用程序的架构,并尝试对数据进行分片和分发(水平可扩展性)。

        最后但并非最不重要的一点是,围绕序列化做了很多工作,序列化的开销(我不是在说 ObjectInputStream 和 ObjectOutputStream 的声誉不佳)并没有那么大。 不仅如此,如果您的数据主要由原始类型(包括字节数组)组成,则有一种有效的方法可以从 堆外 缓冲区中读取 int() 或 readBytes()(例如 netty.io's ChannelBuffer)。这可能是一条路。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-08-26
          • 1970-01-01
          • 1970-01-01
          • 2020-04-30
          • 2012-11-10
          • 1970-01-01
          相关资源
          最近更新 更多