我在CentOS上写了一段java代码来创建500K小文件(平均每个40K)。原始代码是这样的:
package MyTest;
import java.io.*;
public class SimpleWriter {
public static void main(String[] args) {
String dir = args[0];
int fileCount = Integer.parseInt(args[1]);
String content="@#$% SDBSDGSDF ASGSDFFSAGDHFSDSAWE^@$^HNFSGQW%#@&$%^J#%@#^$#UHRGSDSDNDFE$T#@$UERDFASGWQR!@%!@^$#@YEGEQW%!@%!!GSDHWET!^";
StringBuilder sb = new StringBuilder();
int count = 40 * 1024 / content.length();
int remainder = (40 * 1024) % content.length();
for (int i=0; i < count; i++)
{
sb.append(content);
}
if (remainder > 0)
{
sb.append(content.substring(0, remainder));
}
byte[] buf = sb.toString().getBytes();
for (int j=0; j < fileCount; j++)
{
String path = String.format("%s%sTestFile_%d.txt", dir, File.separator, j);
try{
BufferedOutputStream fs = new BufferedOutputStream(new FileOutputStream(path));
fs.write(buf);
fs.close();
}
catch(FileNotFoundException fe)
{
System.out.printf("Hit filenot found exception %s", fe.getMessage());
}
catch(IOException ie)
{
System.out.printf("Hit IO exception %s", ie.getMessage());
}
}
}
}
您可以通过以下命令运行此命令: java -jar SimpleWriter.jar my_test_dir 500000
我认为这是一个简单的代码,但后来我意识到这段代码使用了高达14G的内存。我知道因为当我使用free -m检查内存时,可用内存不断下降,直到我的15G内存VM只剩下70 MB可用内存。我使用Eclipse编译了这个,然后我针对JDK 1.6和JDK1.7编译它。结果是一样的。有趣的是,如果我注释掉fs.write(),只需打开并关闭流,内存就会稳定在某一点。一旦我把fs.write()放回去,内存分配就会疯狂。 500K 40KB文件大约是20G。似乎Java的流编写器在操作期间从不释放其缓冲区。
我曾经以为java GC没时间清理。但是,因为我为每个文件关闭了文件流,所以没有任何意义。我甚至将我的代码转移到C#中,并在Windows下运行,相同的代码生成500K 40KB文件,在某些时候内存稳定,而不是在CentOS下运行14G。至少C#的行为是我的预期,但我无法相信Java以这种方式执行。我问过我的java同事。他们在代码中看不到任何错误,但无法解释为什么会发生这种情况。而且他们承认没有人试图在一个循环中不停地创建500K文件。
我也在线搜索,每个人都说唯一需要注意的是关闭流,我做了。
任何人都可以帮我弄清楚出了什么问题吗?
任何人都可以尝试这个并告诉我你看到了什么吗?
顺便说一下,这个社区中的一些人在Windows上尝试了代码,但似乎运行良好。我没有在Windows上试过它。我只是在Linux中试过,因为我认为人们使用Java的地方。所以,似乎这个问题发生在Linux上)。我还执行了以下操作来限制JVM堆,但它没有任何效果 java -Xmx2048m -jar SimpleWriter.jar my_test_dir 500000
答案 0 :(得分:1)
我试图在Win XP,JDK 1.7.25上测试你的编程。立即获得OutOfMemoryExceptions。
在调试时,只有3000个计数(args [1]),来自此代码的计数变量:
int count = 40 * 1024 * 1024 / content.length();
int remainder = (40 * 1024 * 1024) % content.length();
for (int i = 0; i < count; i++) {
sb.append(content);
}
count是355449.因此,您尝试创建的String将是355449 *内容长,或者在您计算时,长度为40Mb。当我266587时,我失去了记忆,而某人长了31457266。我得到的每个文件都是30Mb。
问题似乎不是内存或GC,而是你打包字符串的方式。
在创建任何文件之前,您是否看到创建的文件或内存耗尽?
我认为你的主要问题是这一行:
int count = 40 * 1024 * 1024 / content.length();
应该是:
int count = 40 * 1024 / content.length();
创建40K,而不是40Mb文件。
答案 1 :(得分:0)
[ Edit2:原帖在本帖末尾留在斜体中]
在评论中做出澄清之后,我已经在Windows机器上运行了您的代码(Java 1.6),这是我的发现(数字来自VisualVM,从任务管理器看到的操作系统内存):
40K大小的示例,写入500K文件(没有参数到JVM): 使用堆:~4M,总堆数:16M,操作系统内存:~16M
40M大小的示例,写入500个文件(参数为JVM -Xms128m -Xmx512m。没有参数,我在创建StringBuilder时出现OutOfMemory错误): 使用堆:~265M,堆大小:~365M,操作系统内存:~365M
特别是从第二个例子中你可以看到我原来的解释仍然存在。是的,有人会期望大部分内存将被释放,因为byte[]
的{{1}}驻留在第一代空间(短期对象)中,但是a)不会立即发生,并且b)当GC时决定踢(它实际上是在我的情况下),是的,它会尝试清除记忆,但它可以清除尽可能多的内存,不一定全部。 GC不提供您可以依赖的任何保证人。
一般来说,你应该给JVM尽可能多的内存。如果你需要为特殊功能保持低内存,你应该尝试一个策略作为我在原始答案中给出的代码示例,即只是不要创建所有这些BufferedOutputStream
对象。
现在在你的CentOS案例中,似乎JVM的行为很奇怪。也许我们可以谈论一个错误或糟糕的实施。要将其归类为泄漏/错误,您应该尝试使用byte[]
来限制堆。另外,请尝试Peter Lawrey suggested以完全不创建-Xmx
(在小文件中),因为您只需一次写入所有字节。
如果它仍然超出内存限制,那么您遇到了泄漏,应该提交错误。 (你仍然可以抱怨,他们将来可能会对其进行优化)。
[编辑1:下面的答案假设OP的代码执行了与写入操作一样多的读取操作,因此内存使用是合理的。 OP澄清了情况并非如此,所以他的问题没有得到解答
“......我的15G内存VM ...”
如果你为JVM提供尽可能多的内存,为什么它应该尝试运行GC?就JVM而言,允许从系统获取尽可能多的内存,并且只有在认为适合这样做时才运行GC。
BufferedOutputStream
的每次执行都会默认分配8K大小的缓冲区。 JVM将尝试仅在需要时回收该内存。这是预期的行为。
不要将您看到的内存从系统的角度和JVM的角度混淆。就系统而言,内存已分配,并将在JVM关闭时释放。就JVM而言,从BufferedOutputStream
分配的所有byte[]
数组都不再使用,它是“免费”内存,如果需要,将被回收。
如果由于某种原因您不希望出现这种情况,可以尝试以下操作:扩展BufferedOutputStream
类(例如创建BufferedOutputStream
类)并添加新方法,例如ReusableBufferedOutputStream
。然后,此方法将清除内部reUseWithStream(OutputStream os)
,刷新并关闭上一个流,重置所有使用的变量并设置新流。您的代码将变为如下所示:
byte[]
使用上述方法,您将避免创建许多// intialize once
ReusableBufferedOutputStream fs = new ReusableBufferedOutputStream();
for (int i=0; i < fileCount; i ++)
{
String path = String.format("%s%sTestFile_%d.txt", dir, File.separator, i);
//set the new stream to be buffered and read
fs.reUseWithStream(new FileOutputStream(path));
fs.write(this._buf, 0, this._buf.length); // this._buf was allocated once, 40K long contain text
}
fs.close(); // Close the stream after we are done
。但是我没有看到预期行为有任何问题,除了“我看到它需要太多内存”之外,你没有提到任何问题。毕竟你已经将它用于使用它。]
答案 2 :(得分:-1)
我认为这与您使用BufferedOutputStream
的事实有关。所有缓冲都可以轻松使用15 GB内存。