【问题标题】:Does the Java VM move objects in memory, and if so - how?Java VM 是否在内存中移动对象,如果是,如何移动?
【发布时间】:2010-09-10 11:19:13
【问题描述】:

Java 虚拟机是否曾经移动内存中的对象,如果是,它如何处理对移动对象的更新引用?

我问是因为我正在探索以分布式方式(即跨多个服务器)存储对象的想法,但出于效率原因,我需要能够在服务器之间移动对象。对象需要能够包含指向彼此的指针,甚至指向远程服务器上的对象。我正在尝试考虑更新对移动对象的引用的最佳方法。

到目前为止,我的两个想法是:

  1. 在对象的生命周期内保持一个引用间接不会移动,如果对象移动,我们会更新它。但是 - 这些间接是如何管理的?
  2. 为每个对象保留一个反向引用列表,这样我们就知道在移动对象时必须更新什么。当然,这会产生性能开销。

我对这些方法的反馈以及对替代方法的任何建议感兴趣。

【问题讨论】:

    标签: java memory-management garbage-collection jvm


    【解决方案1】:

    参考上面关于遍历堆的评论。

    不同的 GC 有不同的处理方式。

    通常在遍历堆时复制收集器,它们不会遍历堆中的所有对象。相反,他们在堆中遍历 LIVE 对象。这意味着如果它可以从“根”对象访问,则该对象是活动的。

    因此,在这个阶段无论如何都必须接触所有活动对象,因为它将它们从旧堆复制到新堆。一旦活动对象的复制完成,旧堆中剩下的就是已经复制的对象,或者是垃圾。此时旧堆可以完全丢弃。

    这种收集器的两个主要好处是它在复制阶段压缩堆,并且它只复制活对象。这对许多系统很重要,因为使用这种收集器,对象分配非常便宜,实际上只是增加堆指针。当 GC 发生时,不会复制任何“死”对象,因此它们不会减慢收集器的速度。事实证明,在动态系统中,临时垃圾比长期存在的垃圾要多得多。

    此外,通过遍历活动对象图,您可以看到 GC 如何“了解”每个对象,并跟踪它们以在复制期间执行任何地址调整目的。

    这不是深入讨论 GC 机制的论坛,因为它是一个不平凡的问题,但这是复制收集器如何工作的基础知识。

    分代复制 GC 会将“较旧”的对象放入不同的堆中,而这些对象最终被收集的频率低于“较新”的堆。理论是持久的对象被提升到老年代并且被收集的越来越少,从而提高了整体 GC 性能。

    【讨论】:

      【解决方案2】:

      您所追求的关键字是“压缩垃圾收集器”。 JVM 允许使用一个,这意味着可以重新定位对象。请查阅您的 JVM 手册以了解您的是否有,并查看是否有任何命令行选项会影响它。

      解释压缩的概念上最简单的方法是假设垃圾收集器冻结所有线程,重新定位对象,在堆和堆栈中搜索对该对象的所有引用,并使用新地址更新它们。实际上它比这更复杂,因为出于性能原因,您不希望在线程停止的情况下执行完整扫描,因此增量垃圾收集器会尽可能地为压缩做准备。

      如果您对间接引用感兴趣,可以先研究 Java 中的弱引用和软引用,以及各种 RPC 系统使用的远程引用。

      【讨论】:

      • 有趣,我会调查的。但是,在整个堆中搜索对象的引用不是非常低效吗?
      • 是和不是。从某种意义上说,这就是“标记”阶段无论如何都必须做的事情,以便找出它计划销毁的对象是否被引用。因此,数百人年的工作已经投入到优化增量垃圾收集器上,以避免一次性完成所有工作。
      【解决方案3】:

      我很想进一步了解您的要求。正如另一个答案所暗示的那样,兵马俑可能正是您正在寻找的。​​p>

      然而,Terracotta 提供的内容与您所要求的内容之间存在细微差别,因此我的询问。

      不同之处在于,就您而言,Terracotta 不提供对对象的“远程”引用——事实上,在使用 Terracotta 时,完全没有 RMI、JMS 等的整个“远程”概念。

      相反,在 Terracotta 中,所有对象都驻留在大型虚拟堆中。线程,无论是节点 1,还是节点 2、节点 3、节点 4 等,都可以访问虚拟堆中的任何对象。

      无需学习特殊的编程或特殊的 API,“虚拟”堆中的对象与本地堆中的对象具有完全相同的行为。

      简而言之,Terracotta 提供的是一种针对多个 JVM 的编程模型,其操作与针对单个 JVM 的编程模型完全相同。单独节点中的线程的行为就像单个节点中的线程一样 - 对象突变、同步、等待、通知在节点之间的行为与线程之间的行为完全相同 - 没有区别。

      此外,与之前的任何解决方案不同,对象引用是跨节点维护的 - 这意味着您可以使用 ==。这都是在集群中维护 Java 内存模型的一部分,这是使“常规”Java(例如 POJO、同步、等待/通知)工作的基本要求(如果您不/不能保留这些都不起作用整个集群的对象标识)。

      所以问题又回到了您身上,以进一步完善您的需求 - 您需要“远程”指针的目的是什么?

      【讨论】:

        【解决方案4】:

        (实际上)任何垃圾收集系统都必须在内存中移动对象以更密集地打包它们并避免碎片问题。

        您正在查看的是一个非常庞大且复杂的主题。我建议您阅读现有的远程对象样式 API:.NET 远程处理和更进一步的技术,如 CORBA

        任何跟踪引用的解决方案都会因为必须处理分布式系统中存在的所有故障模式而变得复杂。 JVM 不必担心会因为网络交换机出现故障而突然发现它看不到一半的堆。

        当您深入了解设计时,我认为其中很多都取决于您希望如何处理不同的失败案例。

        对 cme​​ts 的回应:

        您的问题涉及以分布式方式存储对象,这正是 .NET 远程处理和 CORBA 解决的问题。诚然,这两种技术都不支持这些对象的迁移(AFAIK)。但它们都广泛处理对象身份的概念,这是任何分布式对象系统的关键部分:系统的不同部分如何知道他们在谈论哪些对象。

        我对 Java 垃圾收集器的细节并不太熟悉,而且我确信 Java 和 .NET 垃圾收集器有很多复杂性,以在对应用程序影响最小的情况下实现最高性能。

        不过,垃圾回收的基本思路是:

        • VM 停止所有线程运行托管代码
        • 它从一组已知的“根”执行可达性分析:静态变量、所有线程上的局部变量。对于它找到的每个对象,它都遵循对象内的所有引用。
        • 任何未被可达性分析识别的对象都是垃圾。
        • 然后可以在内存中向下移动仍然存在的对象以密集地打包它们。这意味着对这些对象的任何引用也必须使用新地址进行更新。通过控制何时发生垃圾收集,VM 能够保证没有对象引用“空中”(即保存在机器寄存器中)会导致问题。
        • 一旦进程完成,VM 将再次启动线程执行。

        作为此过程的改进,VM 可以执行分代垃圾收集,其中根据对象的“年龄”维护单独的堆。对象从堆 0 开始,如果它们在多次 GC 中存活,则迁移到堆 1 并最终迁移到堆 2(依此类推 - .NET 仅支持 3 代)。这样做的好处是 GC 可以非常频繁地运行堆 0 集合,而不必担心做工作来证明长期存在的对象(最终在堆 2 中)仍然存在(它们几乎可以肯定是) .

        还有其他改进来支持并发垃圾回收,以及在调度 GC 时实际执行非托管代码的线程的详细信息,这给该领域增加了更多复杂性。

        【讨论】:

        • 老实说,我看不出 .NET 远程处理和 CORBA 与此相关。你能提供更具体的指针吗?
        • 另外,我真正想要的是对垃圾收集系统如何移动对象的解释。
        • @Sanity - Rob Walker 推荐这些技术,以更好地了解他们为什么以及如何做他们所做的事情的策略。
        【解决方案5】:

        听起来您正在寻找分布式缓存,例如 terracotta 或 oracle 的 java 对象缓存(以前称为 tangersol)。

        【讨论】:

        • 我认为您所指的产品称为“Coherence”,公司是Tangosol
        【解决方案6】:

        如果您愿意深入了解,可以查看 JBoss Cache 架构文档并获取一些源代码作为参考。

        这与您描述的不完全一样,但它的工作原理非常相似。

        这是链接。

        http://www.jboss.org/jbosscache/

        我希望这会有所帮助。

        【讨论】:

          猜你喜欢
          • 2018-08-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-18
          • 1970-01-01
          相关资源
          最近更新 更多