Java虚拟机会移动内存中的对象吗?如果会,那么是如何移动的?

15

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

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

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

  1. 维护一个引用间接存储,该存储在对象的生命周期内不会移动,并且在对象移动时进行更新。但是,这些间接存储如何管理呢?
  2. 在每个对象中保留反向引用列表,以便我们知道如果对象被移动,需要更新什么。当然,这会带来性能开销。

我希望听取有关这些方法的反馈,以及任何替代方法的建议。

6个回答

11

关于上面提到的“遍历堆”的评论。

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

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

这种收集器的两个主要优点是,在复制阶段压缩了堆,并且只复制活动对象。对许多系统来说,这很重要,因为使用这种收集器时,对象分配非常便宜,实际上只是增加堆指针。进行GC时,不会复制任何“死”对象,因此它们不会减慢收集器的速度。还发现,在动态系统中,有很多小的、临时的垃圾,比有长期存在的垃圾更多。

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

这不是深入讨论GC机制的论坛,因为这是一个非平凡的问题,但这就是复制收集器的基本工作原理。

生成式复制GC将“较旧”的对象放在不同的堆中,而这些堆比“较新”的堆更少被收集。理论上,持久对象会被提升到较老的代中,并且会越来越少地被收集,从而提高整体GC性能。


3
您需要的关键词是“压缩垃圾收集器”。JVM允许使用它,这意味着对象可以被重新定位。请查阅您的JVM手册以了解它是否支持,并查看是否有任何命令行选项会影响它。
从概念上来说,解释压缩最简单的方法是假设垃圾收集器冻结所有线程,重新定位对象,搜索堆和栈中对该对象的所有引用,并将它们更新为新地址。实际上,它比这更复杂,因为出于性能原因,您不希望在线程停滞时执行完整的扫描,因此增量垃圾收集器将在准备压缩时尽可能地进行工作。
如果您对间接引用感兴趣,您可以首先研究Java中的弱引用和软引用,以及各种RPC系统使用的远程引用。

有趣,我会调查一下。但是,搜索整个堆以查找对象的引用不是非常低效吗? - sanity
是的和不是。从某种意义上说,这正是“标记”阶段必须做的事情,以便找出它计划销毁的对象是否被引用。因此,数百年的工作时间已经投入到优化增量垃圾收集器中,以避免一次性完成所有工作。 - Steve Jessop

3
我很想了解更多关于您的需求的信息。正如另一个答案所建议的那样,Terracotta可能正是您要寻找的东西。
然而,Terracotta提供的内容与您所要求的内容有细微的区别,因此我需要进一步了解。
区别在于,就您而言,Terracotta不提供对“远程”对象的引用 - 实际上,在使用Terracotta时,RMI、JMS等整个“远程”概念都是不存在的。
相反,在Terracotta中,所有对象都驻留在大型虚拟堆中。无论是Node 1、Node 2、Node 3、Node 4等上的线程,都可以访问虚拟堆中的任何对象。
不需要学习特殊编程或特殊API,虚拟堆中的对象与本地堆中的对象具有完全相同的行为。
简而言之,Terracotta提供的是用于多个JVM的编程模型,其操作方式与单个JVM的编程模型完全相同。分离节点中的线程仅像单个节点中的线程一样运行 - 对象变异、同步、等待、通知所有在节点和线程之间完全相同 - 没有任何区别。
此外,与之前的任何解决方案不同,对象引用在节点之间保持不变 - 这意味着您可以使用==。这是保持Java内存模型跨群集的基本要求的一部分,而这对于使“常规”Java(例如POJO、synchronized、wait/notify)工作非常重要(如果您无法在群集中保留对象标识,则所有这些都无法正常工作)。
因此,问题回到您身上,需要进一步细化您的需求 - 您需要“远程”指针的目的是什么?

2
(实际上)任何垃圾回收系统都必须在内存中移动对象以更密集地打包它们,以避免碎片化问题。
您正在查看的是一个非常庞大而复杂的主题。我建议您阅读现有的远程对象样式API:.NET remoting和更早的技术,如CORBA。
跟踪引用的任何解决方案都将受到处理分布式系统中存在的所有故障模式的复杂性的影响。JVM不必担心突然发现由于网络交换机出现故障而看不到其堆的一半。
当您深入设计时,我认为很多问题将归结于您想如何处理不同的故障情况。
对评论的回应:
您的问题涉及以分布式方式存储对象,这正是.NET remoting和CORBA所解决的问题。诚然,两种技术都不支持这些对象的迁移(据我所知)。但它们都广泛涉及对象身份的概念,这是任何分布式对象系统的关键部分:不同部分的系统如何知道它们正在谈论哪些对象。
我对Java垃圾回收器的细节不是特别熟悉,而且我相信Java和.NET垃圾回收器在其中有很多复杂性,以实现最大的性能和对应用程序的最小影响。
然而,垃圾回收的基本思想是:
  • 虚拟机停止所有运行托管代码的线程
  • 它从已知的“根”集合(静态变量、所有线程上的局部变量)执行可达性分析。对于找到的每个对象,它都会跟随对象内的所有引用。
  • 可达性分析未识别的任何对象都是垃圾。
  • 仍然存活的对象可以被移动到内存中紧密打包。这意味着对这些对象的任何引用也必须使用新地址进行更新。通过控制垃圾回收发生的时间,虚拟机能够保证没有对象引用“在空中”(即保存在机器寄存器中)会导致问题。
  • 完成该过程后,虚拟机开始再次执行线程。
作为这一过程的改进,虚拟机可以执行分代垃圾回收,根据对象的“年龄”维护不同的堆。对象最初存放在堆0中,如果它们经历了几次垃圾回收,那么就会迁移到堆1,最终到达堆2(以此类推——.NET仅支持3代)。这样做的好处是,GC可以非常频繁地运行堆0的垃圾回收,并且不必担心证明长期存活的对象(已经进入堆2)仍然存活(它们几乎肯定是存活的)。

还有其他改进支持并发垃圾回收,以及关于线程实际执行未管理代码时GC调度所添加的详细信息,这使得该领域变得更加复杂。


我真的看不出 .NET 远程调用和 CORBA 与此的相关性。你能提供更具体的指针吗? - sanity
另外,我真正想要的是一个关于垃圾回收系统如何移动对象的解释。 - sanity
@Sanity - Rob Walker 推荐使用这些技术作为更好地了解他们的策略,以及他们为什么以及如何做他们所做事情的手段。 - mP.

1

听起来你正在寻找一个分布式缓存,类似于Terracotta或Oracle的Java对象缓存(以前叫Tangersol)。


我认为你所指的产品叫做“Coherence”,公司名为Tangosol。 - Tnilsson

0

如果你愿意深入了解,可以查看JBoss Cache架构文档,并参考其中的一些源代码。

虽然不完全符合你所描述的,但它的工作方式非常相似。

这是链接。

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

希望这能有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接