有没有办法强制JVM使用交换空间,无论内存需求有多大?

18

我的情况是这样的:我有一个需要大量内存的任务,但我没有足够的RAM,无论我尝试了什么(如使用/3gb开关的Jrockit),我都无法为JVM提供足够的内存,操作被异常终止,告诉我需要更多的堆空间。

有没有办法强制JVM使用操作系统的换页机制,以便它不会耗尽内存?这是Windows xp 32位。

虽然这可能需要很长时间,但我并不在意,我只需要完成这个操作。

我已经没有其他选择,也无法控制任何变量。

这是必需的编辑,因为几乎每个人都对我做出了同样的回应:) 这不是我的代码。某人编写了一个工具,将xml文件读入存储库。该工具使用EMF,一次性加载整个模型。我能做的就是向其提供XML文件。

在运行于Windows或Linux等操作系统的本机代码中,操作系统会为其提供内存,并使用虚拟内存/交换空间,应用程序并不知道这一点。我想知道是否可以使JVM也能这样运行。在Windows 32位下,-Xmx可以增加到一定量,但那还不够。目前购买新硬件不是我的选择。所以我想知道是否有可能使JVM像本机进程一样工作,虽然慢但仍然能够正常工作。

显然这是不可能的,我没什么办法了。我只是想知道是否真的没有其他选择。


4
处理无法适应内存的任务的常见方法有两种:
  1. 将任务分成较小的部分,在一个机器上逐个完成,然后合并结果。
  2. 将任务分区,分发到多台机器上进行并行处理,然后再合并结果。
- Abhinav Sarkar
3
如果您真的想使用更多的内存,那么您需要切换到使用64位的JVM(和操作系统),然后您可以告诉Java使用更多的内存,这可能会溢出到交换空间。但是,您最好将问题改为询问如何优化算法以使用更少的内存或其他方式。 - DaveC
4个回答

10

显然,绕过Java堆限制的一种方法是使用直接ByteBuffer存储对象数据。直接字节缓冲区的内容存储在本地进程内存中(而不是堆中),因此您可以依赖操作系统交换机制来对内存进行交换。商业产品BigMemory就是这样实现的,它允许您通过透明地交换到操作系统交换空间和/或磁盘上,实现几乎无限的内存。

实现方法详见该网站(页面搜索“direct byte buffer”)。以下是似乎有些像Java代码的实现示例:

class NativeMemoryCache{
  private Map<Object, ByteBuffer> data = new HashMap<...>();

  public void put(Object key, Serializable object){
    byte[] bytes = serialize(object);
    //allocate native memory to store our object
    ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
    buf.put(bytes);
    buf.flip();
    data.put(key, buf);
  }

  public Object get(Object key){
    ByteBuffer buf = data.get(key).duplicate();
    byte[] bytes = new byte[buf.remaining()];
    buf.get(bytes);
    return deserialize(bytes);
  }

  private byte[] serialize(Object obj){ ... }
  private Object deserialize(byte[] bytes){ ... }
}

希望你能理解这个想法。你只需要实现序列化(你还可以使用zip压缩你的对象。如果你有几个包含可压缩数据(如字符串)的大对象,这将非常有效)。

当然,NativeMemoryCache对象、data哈希映射和key将在堆上,但不应该占用太多内存。


谢谢,这是一种似乎可以解锁JVM堆限制的线索。我一直在想BigMemory是如何工作的。 - mahonya
是的,这很酷。请记住,通过直接字节缓冲区分配的本地内存不会像堆内存一样被回收。当您完成对象时,您需要清除直接字节缓冲区对象(从“data”映射中删除它),否则它将永远保持增长(除非这是您的意图)。 - rodion
有趣的技术。但需要注意的是,这并不能解决原始问题:据我所知,即使使用这种技术,额外的内存也会在JVM的进程空间中分配(尽管不在堆中),因此在32位JVM上仍受4GB/进程的限制。BigMemory似乎更多地涉及避免64位系统上的GC问题,在那里几个GiB的堆不高效(尽管可能,不像32位系统)。 - sleske
1
关于32位操作系统上4GB限制的问题,你是正确的。这种操作系统无法将超过4GB的虚拟内存映射到任何进程中。然而,在JVM的情况下,堆要求通常更为严格,因为JVM需要连续的地址空间。这意味着在某些系统上,您可能无法分配接近4GB(更像是2GB)的内存。对于直接缓冲区,限制总是所有可用的虚拟地址空间(可能不是4GB,但会接近它)。如果您需要更多的内存,那么是的,您将不得不改为64位或使用磁盘交换缓存,如BigMemory或Ehcache。 - rodion

8
正如其他答案所指出的那样,您可以使用-Xmx开关为JVM提供更多RAM。
然而,您可以达到的上限是有限制的。在32位系统上,这可能是2GiB,如果JVM支持的话,可能会达到3或4 GiB。对于Sun JVM,在32位Windows上的限制是1500MiB,根据Java -Xmx, Max memory on system
由于基本架构原因,进程不能(没有特殊技术的情况下)获取超过4 GiB的内存(包括它可能使用的任何交换空间),这就是为什么存在-Xmx值的限制。
如果您已经尝试了最大可能的值,仍然遇到OOM错误,则您唯一的选择是:
修复应用程序,使其需要更少的RAM
或者
将其移动到64位操作系统,并进一步增加-Xmx 编辑:
请注意,4 GiB限制是CPU架构的限制,因此适用于任何进程,无论是Java还是其他进程。因此,即使使用本地分配技巧也无法在此处帮助您。唯一的绕过方法是使用多个进程,但这将需要对应用程序进行根本性重写,这可能与修复应用程序以使用更少RAM一样复杂。因此,上述两个选项是您唯一(明智的)选择。
编辑2:
针对您问题的新部分:
我想知道是否有可能使JVM像本地进程一样工作。
这是一个误解。JVM在这方面确实像本地进程一样工作:它使用的堆位于由JVM从操作系统分配的内存中;对于操作系统来说,这只是分配的内存,如果需要,操作系统会像处理任何其他内存一样将其交换出去-没有什么特别之处。
堆不能无限增长的原因不是它不能比物理RAM更大(它可以,至少在Linux/x86上我尝试过),而是每个操作系统进程(JVM是)不能获得超过4GiB RAM。因此,在32位系统上,您永远无法拥有超过4GiB的堆。在实践中,它可能要小得多,因为堆内存不能被碎片化(例如,请参见Java maximum memory on Windows XP),但4 GiB是一个固定、不可避免的限制。

4
根据我的经验,JVM会从操作系统请求内存,可以在RAM或交换空间中分配内存。这取决于您拥有多少资源。您可以在运行JVM时指定的命令行选项-Xmx而不是RAM来分配Java中的内存。例如,如果RAM中没有足够的内存,则JVM会从交换空间中接收内存,甚至可能不知道这一点。
顺便说一句,我认为您真的不需要那么多内存。我同意那些说法。建议您检查设计。

0

如果您的RAM不足,您需要更改代码以使应用程序适合内存。如果你让JVM变得足够大,它必须交换到磁盘上的应用程序会几乎无法运行。JVM中的堆不设计为从磁盘上运行。

我怀疑您遇到的问题是不能分配足够的连续内存,这是JVM的要求。随着您使用更多可用内存,要获得32位操作系统下的大型连续内存块将变得更加困难。

现在是时候购买更多的内存了,这在现今相对便宜。或者减少您的内存需求。使用交换会花费很长时间才能完成。

另外,您可以花费约1800英镑购买一台24GB服务器,约4200英镑购买一台64GB服务器。花费53,000英镑,您可以获得1TB内存的服务器!:D


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