Java VM是否会在内存中移动对象?
Java虚拟机是否曾经在内存中移动对象,如果是,它将如何处理对移动对象的更新引用?
我问是因为我正在探索一种以分布式方式(即,跨多个服务器)存储对象的想法,但是出于效率考虑,我需要能够在服务器之间移动对象。对象需要能够包含指向彼此的指针,甚至指向远程服务器上的对象。我正在尝试考虑更新对移动对象的引用的最佳方法。
到目前为止,我的两个想法是:
- 在对象的整个生命周期中都不会移动的地方保持引用间接,如果对象移动,我们将更新该引用。但是-如何管理这些间接?
- 保留每个对象的反向引用列表,因此我们知道如果对象移动了必须更新的内容。当然,这会产生性能开销。
我会对这些方法的反馈以及其他替代方法的建议感兴趣。
解决方案
(实际上)任何垃圾收集系统都必须在内存中移动对象,以使其更密集地打包,并避免碎片问题。
我们正在看的是一个非常大而复杂的主题。我建议我们阅读现有的远程对象样式API:.NET远程处理并进一步研究CORBA等技术
跟踪引用的任何解决方案都会因必须处理分布式系统中存在的所有故障模式而变得复杂。 JVM不必担心突然发现由于网络交换机故障而无法看到一半的堆。
当我们深入研究设计时,我认为其中很多将取决于我们要如何处理不同的失败案例。
对评论的回应:
问题是关于以分布式方式存储对象的,这正是.NET远程处理和CORBA的地址。不可否认,两种技术都不支持这些对象的迁移(AFAIK)。但是它们都广泛地处理了对象身份的概念,这是任何分布式对象系统的关键部分:系统的不同部分如何知道他们在谈论哪个对象。
我对Java垃圾收集器的细节不是很熟悉,并且我确信Java和.NET垃圾收集器在它们中有很多复杂性,以在不影响应用程序的情况下获得最佳性能。
但是,垃圾回收的基本思想是:
- VM停止所有线程运行托管代码
- 它根据已知的"根"集执行可达性分析:静态变量,所有线程上的局部变量。对于每个对象,它都会找到该对象内的所有引用。
- 可达性分析未标识的任何对象都是垃圾。
- 然后可以将仍然存在的对象向下移动到内存中以密集地打包它们。这意味着对这些对象的任何引用也必须使用新地址进行更新。通过控制何时可能发生垃圾回收,VM可以保证没有"空中传播"的对象引用(即,保存在机器寄存器中)会引起问题。
- 一旦完成该过程,VM便会开始再次执行线程。
作为对此过程的改进,VM可以执行世代垃圾收集,其中根据对象的"年龄"维护单独的堆。对象从堆0开始,如果它们在多个GC中幸存下来,则迁移到堆1,最后迁移到堆2(依此类推,.NET仅支持3代)。这样做的好处是,GC可以非常频繁地运行堆0集合,而不必担心进行工作以证明寿命长的对象(已结束在堆2中)仍然有效(它们几乎肯定是存在的) 。
还有其他改进来支持并发垃圾回收,并且安排了GC时实际上正在执行非托管代码的线程的详细信息为该领域增加了更多的复杂性。
我们要使用的关键字是"压缩垃圾收集器"。允许JVM使用一个,这意味着可以重定位对象。请查阅JVM手册,以了解JVM手册,并查看是否有任何命令行选项会影响它。
从概念上讲,最简单的解释压缩的方法是假定垃圾收集器冻结所有线程,重新定位对象,在堆和堆栈中搜索对该对象的所有引用,并用新地址更新它们。实际上,它要复杂得多,因为出于性能方面的考虑,我们不想在线程停止的情况下执行完整扫描,因此增量垃圾收集器会在可能的情况下为压缩做准备。
如果我们对间接引用感兴趣,则可以从研究Java中的弱引用和软引用以及各种RPC系统使用的远程引用开始。
参考上面关于遍历堆的注释。
不同的GC采取不同的方式。
通常,在遍历堆时复制收集器,但它们不会遍历堆中的所有对象。相反,它们将实况对象放在堆中。含义是,如果可以从"根"对象访问该对象,则该对象是活动的。
因此,在此阶段,无论如何都要触摸所有活动对象,因为它会将它们从旧堆复制到新堆。一旦完成活动对象的复制,旧堆中剩下的全部或者是已经复制的对象,或者是垃圾。到那时,旧堆可以被完全丢弃。
这种收集器的两个主要好处是,它在复制阶段压缩了堆,并且仅复制了活动对象。这对于许多系统而言很重要,因为使用这种收集器,对象分配非常便宜,实际上只不过是增加堆指针而已。当发生GC时,不会复制任何"死"对象,因此它们不会降低收集器的速度。事实证明,在动态系统中,临时垃圾比长期存在的垃圾要多得多。
另外,通过遍历活动对象图,我们可以看到GC如何"了解"每个对象,并跟踪它们,以便在复制期间执行任何地址调整用途。
这不是一个讨论GC机理的论坛,因为这不是一个小问题,但这是复制收集器工作原理的基础。
世代复制的GC会将"较旧"的对象放在不同的堆中,与"较新"的堆相比,这些对象最终得到的收集较少。从理论上讲,持久耐用的物体会被提升到更老的世代,并且越来越少地被收集起来,从而改善了整体GC的性能。
听起来我们正在寻找分布式缓存,例如兵马俑或者oracle的Java对象缓存(以前称为tangersol)。
我很想知道更多关于要求。另一个答案表明,兵马俑可能正是我们想要的。
但是,Terracotta提供的产品与我们要的产品之间存在细微的差异,因此是我的询问。
区别在于,就我们而言,Terracotta并不提供对对象的"远程"引用,实际上,在使用Terracotta时,完全不存在RMI,JMS等的整个"远程"概念。
而是在Terracotta中,所有对象都驻留在大型虚拟堆中。线程,无论在节点1上,在节点2上,在节点3上,在节点4上等等,都可以访问虚拟堆中的任何对象。
不需要学习特殊的程序,也不需要特殊的API,"虚拟"堆中的对象的行为与本地堆中的对象完全相同。
简而言之,Terracotta提供的是用于多个JVM的编程模型,其运行方式与单个JVM的编程模型完全相同。单独节点中的线程的行为就像单节点对象中的线程一样,对象的突变,同步,等待,通知在节点之间的行为完全相同,而在线程之间没有任何区别。
此外,与之前提出的任何解决方案不同,对象引用跨节点维护,这意味着我们可以使用==。这是在整个集群中维护Java内存模型的一部分,这是使"常规" Java(例如POJO,同步,等待/通知)正常工作的基本要求(如果我们不/无法保留,则这些都不起作用整个集群中的对象身份)。
因此,问题又回到了角度,以进一步完善要求,以达到"远程"指针的目的?
如果我们愿意深入研究,可以查看JBoss Cache体系结构文档,并获取其一些源代码作为参考。
这与我们描述的不完全相同,但是其工作原理非常相似。
这是链接。
http://www.jboss.org/jbosscache/
我希望这有帮助。