我是Java的新手,正在阅读非常大的文件,需要一些帮助来理解问题并解决它。我们有一些遗留代码必须进行优化才能使其正常运行。文件大小只能从10mb到10gb不等。只有当文件超过800mb时才会启动麻烦。
InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
byte[] localbuffer = new byte[2048];
ByteArrayOutputStream bArrStream = new ByteArrayOutputStream();
int i = 0;
while (-1 != (i = inFileReader.read(buffer))) {
bArrStream.write(localbuffer, 0, i);
}
byte[] data = bArrStream.toByteArray();
inFileReader.close();
bos.close();
我们收到错误
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2271)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140)
任何帮助都会受到赞赏吗?
答案 0 :(得分:18)
尝试使用 java.nio.MappedByteBuffer 。
http://docs.oracle.com/javase/7/docs/api/java/nio/MappedByteBuffer.html
您可以将文件的内容映射到内存而无需手动复制。高级操作系统提供内存映射,Java具有API以利用该功能。
如果我的理解是正确的,内存映射不会将文件的整个内容加载到内存中(意思是“根据需要部分加载和卸载”),所以我猜一个10GB的文件不会占用你的内存。
答案 1 :(得分:12)
即使你可以增加JVM内存限制,它也是不必要的,并且分配一个像10GB这样的巨大内存来处理文件听起来过度和资源密集。
目前您正在使用" ByteArrayOutputStream"它保留了内部存储器以保存数据。代码中的这一行会将最后读取的2KB文件块追加到此缓冲区的末尾:
bArrStream.write(localbuffer, 0, i);
bArrStream不断增长,并最终耗尽内存。
相反,您应该重新组织算法并以流式方式处理文件:
InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
byte[] localbuffer = new byte[2048];
int i = 0;
while (-1 != (i = inFileReader.read(buffer))) {
//Deal with the current read 2KB file chunk here
}
inFileReader.close();
答案 2 :(得分:7)
Java虚拟机(JVM)以固定的内存上限运行,您可以这样修改:
java -Xmx1024m ....
e.g。上面的选项(-Xmx ...)将限制设置为1024兆字节。您可以根据需要进行修改(在您的机器,操作系统等的限制范围内)。请注意,这与传统应用程序不同,后者将根据需要从操作系统分配越来越多的内存。
然而,更好的解决方案是重新设计应用程序,这样您就不需要一次性将整个文件加载到内存中。这样您就不必调整JVM,也不会占用大量内存。
答案 3 :(得分:5)
使用命令行选项-Xmx运行Java,该选项设置堆的最大大小。
答案 4 :(得分:4)
您无法在内存中读取10GB文本文件。你必须首先读取X MB,用它做一些事情,然后阅读下一个X MB。
答案 5 :(得分:4)
尝试使用大缓冲区读取大小可能是10 MB,然后检查。
答案 6 :(得分:4)
问题是你正在做的事情所固有的。将整个文件读入内存始终是一个坏主意。除非你有一些令人吃惊的硬件,否则你真的无法用现有技术将10GB文件读入内存。找到一种逐行处理,按记录记录,按块块处理的方法......
答案 7 :(得分:4)
是否必须获得整个ByteArray()
输出流?
byte[] data = bArrStream.toByteArray();
最佳方法是逐行阅读&逐行写。您可以使用BufferedReader
或Scanner
来阅读大文件,如下所示。
import java.io.*;
import java.util.*;
public class FileReadExample {
public static void main(String args[]) throws FileNotFoundException {
File fileObj = new File(args[0]);
long t1 = System.currentTimeMillis();
try {
// BufferedReader object for reading the file
BufferedReader br = new BufferedReader(new FileReader(fileObj));
// Reading each line of file using BufferedReader class
String str;
while ( (str = br.readLine()) != null) {
System.out.println(str);
}
}catch(Exception err){
err.printStackTrace();
}
long t2 = System.currentTimeMillis();
System.out.println("Time taken for BufferedReader:"+(t2-t1));
t1 = System.currentTimeMillis();
try (
// Scanner object for reading the file
Scanner scnr = new Scanner(fileObj);) {
// Reading each line of file using Scanner class
while (scnr.hasNextLine()) {
String strLine = scnr.nextLine();
// print data on console
System.out.println(strLine);
}
}
t2 = System.currentTimeMillis();
System.out.println("Time taken for scanner:"+(t2-t1));
}
}
您可以在上面的示例中将System.out
替换为ByteArrayOutputStream
。
请查看以下文章了解更多详情:Read Large File
看看相关的SE问题:
答案 8 :(得分:3)
ByteArrayOutputStream
写入内存缓冲区。如果这确实是您希望它工作的方式,那么您必须在输入的最大可能大小之后调整JVM堆的大小。此外,如果可能,您可以在开始处理之前检查输入大小,以节省时间和资源。
另一种方法是流式解决方案,其中运行时使用的内存量是已知的(可能是可配置的,但在程序启动之前仍然已知),但如果它可行或不可行完全取决于您的应用程序的域(因为您可以' t不再使用内存缓冲区)如果您不想/不想更改它,可能还有其余代码的架构。
答案 9 :(得分:3)
您好我假设您正在阅读大型txt文件并逐行设置数据,使用逐行阅读方法。据我所知,你可以阅读高达6GB的可能更多。我强烈建议你尝试这种方法。
DATA1 DATA2 ...
// Open the file
FileInputStream fstream = new FileInputStream("textfile.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fstream));
String strLine;
//Read File Line By Line
while ((strLine = br.readLine()) != null) {
// Print the content on the console
System.out.println (strLine);
}
//Close the input stream
br.close();
答案 10 :(得分:3)
按行迭代地读取文件。这将显着减少内存消耗。或者你可以使用
FileUtils.lineIterator(theFile,“UTF-8”);
由Apache Commons IO提供。
@Entity
public class EntityClass{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public int id;
public EntityClass() {
}
...
}
}
答案 11 :(得分:2)
答案 12 :(得分:2)
简短回答,
没有做任何事情,你可以将电流限制推高1.5倍。这意味着,如果您能够处理800MB,则可以处理1200 MB。这也意味着如果通过java -Xm ....
的一些技巧你可以移动到当前代码可以处理7GB的点,你的问题就解决了,因为1.5因素将带你到10.5GB,假设你有空间可用在您的系统上,JVM可以获得它。
答案很长:
该错误非常具有自我描述性。您达到了配置的实际内存限制。有很多关于你可以使用JVM的限制的猜测,我对此知之甚少,因为我找不到任何官方信息。但是,您将以某种方式受限于可用交换,内核地址空间使用,内存碎片等约束。
现在发生的事情是ByteArrayOutputStream
对象是使用大小为32的默认缓冲区创建的,如果你没有提供任何大小(这是你的情况)。每当您在对象上调用write
方法时,都会启动一个内部机制。似乎与错误输出完全匹配的openjdk implementation release 7u40-b43使用内部方法ensureCapacity
来检查缓冲区是否有足够的空间放置要写入的字节。如果没有足够的空间,则调用另一个内部方法grow
来增加缓冲区的大小。方法grow
定义了适当的大小,并从类copyOf
调用方法Arrays
来完成工作。
缓冲区的适当大小是当前大小和保持所有内容(当前内容和要写入的新内容)所需的大小之间的最大值。
类copyOf
(follow the link)中的方法Arrays
为新缓冲区分配空间,将旧缓冲区的内容复制到新缓冲区并将其返回到grow
在为新缓冲区分配空间时出现问题,在一些write
之后,您到达了可用内存耗尽的点:java.lang.OutOfMemoryError: Java heap space
。
如果我们查看细节,你会阅读2048年的大块。所以
您的描述中不清楚的是,您可以以某种方式读取高达800MB,但不能超越。你必须向我解释一下。
我希望你的限制恰好是2的幂(或者如果我们使用10个单位的功率,那么关闭)。在这方面,我希望你立即开始遇到麻烦,高于其中一个:256MB,512 MB,1GB,2GB等。
当你达到这个限制时,并不意味着你的内存不足,它只是意味着不可能分配另一个缓冲区,它的大小是你已经拥有的缓冲区的两倍。这一观察结果为您的工作提供了改进的空间:找到您可以分配的最大缓冲区大小,并通过调用相应的构造函数预先保留它
ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(myMaxSize);
它的优点是可以减少在引擎盖下发生的开销后台内存分配,让您满意。通过这样做,您将能够达到1.5现在的限制。这只是因为缓冲区的最后一次增加,它从当前大小的一半变为当前大小,并且在某些时候你将当前缓冲区和旧缓冲区一起放在内存中。但是你不能超过你现在的限制的3倍。解释完全一样。
话虽如此,我没有任何神奇的建议来解决问题,除了通过给定大小的块处理你的数据,一次一块。另一个好方法是使用Takahiko Kawasaki的建议并使用MappedByteBuffer
。请记住,在任何情况下,您都需要至少10 GB的物理内存或交换内存才能加载10GB的文件。
见
答案 13 :(得分:0)
在考虑之后,我决定回答第二个问题。我考虑了第二个答案的优点和缺点,其优点是值得的。所以就是这样。
大多数建议的注意事项都忘记了一个特定的事实:Java中可以包含数组的大小(包括ByteArrayOutputStream
)的内置限制。并且该限制由最大int
值决定,该值为2 ^ 31-1(略小于2Giga)。这意味着您最多只能读取2 GB(-1字节)并将其放在一个ByteArrayOutputStream
中。如果VM需要更多控制,则数组大小的限制实际上可能更小。
我的建议是使用ArrayList
byte[]
而不是一个byte[]
来保存文件的完整内容。并且在将ByteArrayOutputStream
放入最终的data
数组之前,还要删除放入InputStream inFileReader = channelSFtp.get(path); // file reading from ssh.
// good habits are good, define a buffer size
final int BUF_SIZE = (int)(Math.pow(2,30)); //1GB, let's not go close to the limit
byte[] localbuffer = new byte[BUF_SIZE];
int i = 0;
while (-1 != (i = inFileReader.read(localbuffer))) {
if(i<BUF_SIZE){
data.add( Arrays.copyOf(localbuffer, i) )
// No need to reallocate the reading buffer, we copied the data
}else{
data.add(localbuffer)
// reallocate the reading buffer
localbuffer = new byte[BUF_SIZE]
}
}
inFileReader.close();
// Process your data, keep in mind that you have a list of buffers.
// So you need to loop over the list
的非必要步骤。以下是基于原始代码的示例:
-Xms
只需运行程序就可以在具有足够物理内存或交换的64位系统上正常运行。现在,如果您想加快速度以帮助VM在开始时正确地填充堆,请使用选项-Xmx
和java -Xms12288m -Xmx12288m YourApp
运行。例如,如果您希望12GB的堆能够处理10GB文件,请使用=IF(IF(A4 = "",INDIRECT(ADDRESS(5,1 + COUNTIF($A$4:A4,"") - 1)),A4)=0,"",IF(A4 = "",INDIRECT(ADDRESS(5,1 + COUNTIF($A$4:A4,"") - 1)),A4))