【问题标题】:How to allocate the memory from OS instead of increasing the JVM’s heap size?如何从 OS 分配内存而不是增加 JVM 的堆大小?
【发布时间】:2010-12-11 15:44:19
【问题描述】:

我需要检测我附加到电子邮件的文件是否超出服务器限制。我不允许增加 JVM 堆大小来执行此操作,因为它会影响应用程序的性能。

如果我不增加JVM堆大小,我会直接遇到OutOfMemoryError。

我想知道如何从操作系统分配内存而不是增加 JVM 的堆大小?

非常感谢!

【问题讨论】:

  • 我不明白为什么你需要大量的堆大小来检查文件的大小。当然,您不会一次将整个文件读入内存。
  • 向我们展示您是如何阅读文件的,我们将 a) 向您展示如何更有效地做到这一点,然后 b) 告诉您你为什么你目前的方法不好。老实说,您提出的问题让我们相信您正在以一种可怕的方式完成这项任务。 一种令人毛骨悚然的可怕方式。
  • JavaMail 要将附件保存在内存中的基本问题,所以您需要在将其提供给 JavaMail 之前进行测试?

标签: java memory-management heap-memory


【解决方案1】:

您是否真的试图读取整个文件以确定其大小以检查它是否小于某个配置值(您的问题不太容易理解)?如果是这样,您为什么不使用 File#length() 代替?

【讨论】:

  • 我可以在将文件上传到服务器之前读取文件大小。但是,服务器团队没有适当的 API 来告诉我关于最大文件限制的服务器策略是什么。我必须将文件流式传输到服务器并让它确定文件是否超过限制。这效率不高。由于我无法增加 JVM 堆大小,我如何分配操作系统内存来执行此操作?非常感谢。
  • 你说的是客户端还是服务器的堆大小?您在哪里需要更多内存,具体原因是什么?
  • 我说的是客户端的堆大小。我需要在客户端读取这个大文件。之后我需要将此文件流式传输到服务器。服务器将能够根据其策略确定文件是否超过了文件大小限制。服务器团队没有告诉我文件大小策略是什么的 API。我需要在客户端分配更多内存才能读取这个大文件。我需要在操作系统中分配更多内存才能这样做。我想知道我可以使用什么 java API 来做到这一点。谢谢。
  • 为什么需要从客户端代码读取整个文件以将其流式传输到服务器?
  • 是的,您刚刚在上一条评论中写道。我问为什么你必须将整个文件保存在客户端内存中才能做到这一点。
【解决方案2】:

如果您需要将文件流式传输到服务器以检查它是否太大,您仍然不需要将整个文件读入内存。

相反,将 10-100k 读入内存。填充缓冲区,将其发送到服务器。重复直到文件完成或服务器抱怨。那么整个文件就不需要足够的内存了。

【讨论】:

  • 你好,我刚刚在上面展示了我的代码。我正在使用 FileInputStream 将文件读入内存....你能告诉我一些关于使用缓冲区将 10-100K 读入内存的伪代码吗?我们的 JVM 堆大小为 256MB。非常感谢。
  • 作为起点,您可以查看FileInputStream.read(byte[] buffer);它将占用一个缓冲区(您应该设置适当的大小)并读入它,直到缓冲区用完或文件用完。记住要保持它返回的大小——不能保证它会一次读取多少——并将那么多从缓冲区写入传出流。重复直到read() 返回-1,这意味着文件中没有任何内容。其他人可能知道您需要插入的任何其他输入或输出流,以使其在现实世界中更好地工作。
【解决方案3】:

如果您编写自己的流处理代码,则可以创建自己的计数器来跟踪传输的字节数。如果还没有某种过滤器类可以为您执行此操作,我会感到惊讶。 Sun has a page about this。搜索“CountReader”。

【讨论】:

    【解决方案4】:

    可以通过本机代码和 JNI 本地分配内存。然而,这听起来很痛苦。

    你不能给JVM合适的内存配置(通过-Xmx)吗?如果您要邮寄的文档太大以至于您无法轻松处理它,那么我不确定电子邮件是否是传输它的正确媒介,您应该改为托管它并发送链接到它,或者可能是 FTP。

    【讨论】:

    • 感谢您对此的快速回复。我想将 Xmx 设置从 256m 更改为 512m,但这个解决方案被推迟了。我正在处理两个典型的用例: -- 在向服务器发送带有大附件的电子邮件之前,尝试检测附件是否超出了服务器策略。 -- 在上传到服务器之前尝试检测单个文件的大小。现在的问题是服务器团队没有一个 API 可以告诉我服务器大小限制的策略是什么……所以我必须先尝试上传文件,这效率不高……
    【解决方案5】:

    如果所有其他解决方案都无法使用(我鼓励您找到一种比要求整个文件适合内存更好的方法!)您可以考虑使用直接 ByteBuffer。它可以选择使用 mmap() 或其他系统调用将文件映射到内存中,而无需实际读取/分配堆中的空间。您可以通过在 FileChannel 上调用 map() 来做到这一点 -- API documentation。请注意,这可能很昂贵和/或在某些平台上不受支持,因此与不需要整个文件都在内存中的任何解决方案相比,它应该被视为次优。

    【讨论】:

      【解决方案6】:
      Socket s = /* go get your socket to the server */
      InputStream is = new FileInputStream("foo.txt");
      OutputStream os = s.getOutputStream();
      byte[] buf = new byte[4096];
      for(int len=-1;(len=is.read(buf))!=-1;) os.write(buf,0,len);
      os.close();
      is.close();
      

      当然要处理你的异常。

      【讨论】:

        【解决方案7】:

        如果由于内存限制而不允许增加堆大小,则执行“表下”内存分配会导致同样的问题。听起来您正在寻找规则中的漏洞。比如,“我的医生说要减少我每餐吃的量,所以我在两餐之间吃得更多来弥补。”

        我所知道的不使用 Java 堆分配内存的唯一方法是编写 JNDI 调用以使用 C 对内存进行 malloc。但是您将如何使用这些内存呢?您必须编写更多的 JNDI 调用才能与之交互。我认为你最终会基本上重新发明 Java。

        如果这里的目标是发送一个大文件,只需使用缓冲流并一次读取/写入一个字节。顾名思义,缓冲流将为您处理缓冲,因此您不会一次真正地敲击硬盘驱动器一个字节。它会真正读取,我认为默认值是一次 8k,然后在您要求时将这些字节传递给您。同样,在写入端,它将节省几个 kb 并将它们全部发送到块中。

        所以您要做的就是打开一个 BufferedInputStream 和一个 BufferedOutputStream。然后编写一个循环,从输入流中读取一个字节并将其写入输出流,直到到达文件结尾。

        类似:

        OutputStream os=... however you're getting your socket ...
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream(fileObject));
        BufferedOutputStream bos=new BufferedOutputStream(os);
        int b;
        while ((b=bis.read())!=-1)
          bos.write(b);
        bis.close();
        bos.close();
        

        无需通过重新发明缓冲来让自己的生活变得复杂。 而(

        【讨论】:

          猜你喜欢
          • 2016-07-18
          • 1970-01-01
          • 2011-07-24
          • 2010-11-29
          • 1970-01-01
          • 2011-09-21
          • 1970-01-01
          • 1970-01-01
          • 2013-01-01
          相关资源
          最近更新 更多