15

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

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

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

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

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

4

6 回答 6

11

参考上面关于走堆的评论。

不同的 GC 有不同的方法。

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

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

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

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

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

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

于 2008-09-18T01:43:45.467 回答
3

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

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

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

于 2008-09-18T00:40:29.643 回答
3

我很想知道更多关于您的要求。正如另一个答案所暗示的那样,兵马俑可能正是您正在寻找的。

然而,Terracotta 提供的内容与您所要求的内容之间存在细微差别,因此我进行了调查。

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

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

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

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

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

So the question comes back to you to further refine your requiements - for what purpose do you need "remote" pointers?

于 2008-09-30T06:06:39.503 回答
2

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

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

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

当您深入研究设计时,我认为很多问题将归结为您希望如何处理不同的失败案例。

回复评论:

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

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

但是,垃圾收集的基本思想是:

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

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

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

于 2008-09-18T00:27:15.820 回答
1

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

于 2008-09-18T03:24:05.020 回答
0

If you are willing to go that deep down, you can take a look to JBoss Cache architecture docs and grab some of its source code as reference.

This is not exactly what you described, but it works very similar.

Here's the link.

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

I hope this helps.

于 2008-09-30T16:23:56.213 回答