指定Java内存分配池地址。

4

使用-Xms-Xmx选项,可以设置内存分配池的初始大小和最大大小。在Linux和AIX上使用strace/truss,我发现JVM内部使用了(k)mmap系统调用。address参数为NULL,因此操作系统决定将内存映射到哪个虚拟内存地址。

$ truss java -Xmx512M Hello 2>&1 | grep mmap
kmmap(0x00000000, 536870912, 3, 17, -1, 0x00000000, 0x00000000) = 0xB0000000

能否指定这个地址?

背景:我需要通过Java Native Interface (JNI)调用遗留代码,该代码需要大量的不可重定位数据(32位地址空间中的2 GB),映射到内存中的特定位置。这个区域与Java内存分配池的位置重叠。

编辑:以下是实际的内存布局:

0x0... AIX
0x1... Text
0x2... Stack
0x3... Heap
0x4... Heap
...... Legacy Data (2 GB)
0xd... Shared Library Text
0xe... unused
0xf... Shared Library Data

我的目标是将Java内存分配池从0xb/0xc移动到0x3/0x4段,这也适用于标准(非大型)内存模型。


你能否编辑遗留代码,使其内存位置可配置? - Andy Thomas
需要大量的数据,有多少MB或GB的估计? - Favonius
3个回答

2
个人而言,我从未听说过配置分配池地址的方法。如果有这样的设置,那么它可能非常隐蔽。
唯一确定的方法是查看JDK/JRE的源代码,可在http://hg.openjdk.java.net/上找到。如果NULL参数是硬编码的,那么就没有办法了。
对于另一种解决方案,如果您无法修复不良的本地代码:
您可以编写一个小型本地程序来调用您的本地代码,然后通过Java(通过Runtime.exec)调用该程序。这样,淘气的代码就得到了自己的操作系统进程和独立的地址空间。当然,如果本地代码和Java之间的交互不太频繁,这只是可行的。
或将代码变成一个小型服务器,与您的Java应用程序并行运行,以便两者之间可以通信...
附加说明:
我刚刚注意到mmap的man页面中提到,给mmap提供地址并不一定被支持:
MAP_FIXED:精确解释addr。 [...] 是否支持MAP_FIXED是实现定义的。 在符合XSI的系统上,应支持MAP_FIXED。[...]当未设置MAP_FIXED时,实现以实现定义的方式使用addr来到达pa。

http://linux.die.net/man/3/mmap

所以,即使您成功让JVM使用特定地址调用mmap,也可能仍然无法成功。

感谢您提供源代码提示。也许我们需要编译自己的Java可执行文件。但我宁愿避免这样做。我无法找到正确的mmap,它位于hotspot/src/os/linux/launcher/java.c中。 - Frank
遗留代码听起来很疯狂。将其分叉为自己的进程,以便它可以执行自己的疯狂操作,使用一个小包装器,让您可以通过标准输入向其发送请求,并从其标准输出获取响应。如果尝试这样做速度不够快,请使用共享内存进行通信-在Java端分配一个直接的ByteBuffer,然后使用一点JNI来设置共享映射。在遗留代码进程中共享该映射。 - Tom Anderson
@Tom:共享内存的技巧似乎是个好主意;当我提议将代码放入一个独立的进程中时,没有想到这一点。 - sleske
谢谢。我担心使用它会有点麻烦,所以我肯定会先尝试流! - Tom Anderson

2
也许你可以反其道而行之。启动一个本地进程,然后做必要的操作来保留你不可谈判的空间(使用mmap或其他方式)。然后使用Java的调用API创建并启动一个JVM在你的本地进程中。我找不到任何关于该JVM如何进行内存管理的文档,但很有道理它会与已经由主机进程分配的内存友好地协作(我猜测它只是使用本地malloc),因此它会找到另一个地方来放置它的堆。如果您将0x3-0x4区域保留未分配,您可以希望它将其放置在那里。
然而,我认为在您的内存模型中可能没有足够的地址空间来容纳JVM,存在真正的风险。0x3-0x4区域是512 MB吗?如果您可以将整个JVM(堆栈、perm、VM结构等)放在那里,那就好了,但如果不能,当JVM无法为堆分配连续的内存时,它可能会变得非常不安稳。或许不会。我真的不知道。

因此,在提出这个建议后,我强烈建议您不要这样做,而是遵循sleske的建议并将本地代码放入单独的进程中。


使用建议的Java调用API,我得到了一个可工作的C++原型。在小内存模型中,AIX将堆栈和堆放入0x2段中。然后,该原型映射从0x5段开始的2 GB。最后,它创建了一个JVM并调用了主方法,仅为Java留下了0x3段。是的,我完全意识到这是高度特定于平台、不可移植、危险和丑陋的,但这是远离Corba铁腕控制的第一步。耶。 - Frank
@Frank:我认为它应该是相当平台无关和可移植的。对于其他形容词不发表评论! - Tom Anderson
不错的技巧。我以前从未听说过这个“调用API”(尽管我曾想知道例如Oracle如何将JVM嵌入其DBMS中)。好知道。 - sleske

1

你可以尝试的一个想法是使用LD_PRELOAD来注入自己的kmmap行为。Google在TCMalloc项目中提供了一个LD_PRELOAD示例,用于注入自定义malloc。


你和汤姆的想法听起来都很不错。我会在回到工作岗位后尝试两种方法,但这需要等待八个小时。目前,编写一个(希望)合理的服务器还不太可能,我很抱歉。感谢你们的建议,晚安;-) - Frank
这听起来像是一个粗糙而危险的解决方案。另一方面,这是一个危险而粗糙的问题,所以,遗憾的是,这可能是适当的 :-/. - sleske

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