2013-08-29 60 views
15

Tôi mới sử dụng Java và làm việc để đọc các tệp rất lớn, cần một số trợ giúp để hiểu vấn đề và giải quyết nó. Chúng tôi đã có một số mã di sản mà phải được tối ưu hóa để làm cho nó chạy đúng. Kích thước tập tin có thể thay đổi từ 10mb đến 10gb chỉ. chỉ gặp sự cố khi tệp bắt đầu vượt quá kích thước 800MB.Java OutOfMemoryError khi đọc một tệp văn bản lớn

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(); 

Chúng tôi đang nhận được lỗi

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) 

Bất kỳ trợ giúp sẽ được đánh giá cao?

+1

Trong mã mẫu bạn đã đề cập, bạn chỉ cần tải toàn bộ tệp trong 'ByteArrayOutputStream'. Trường hợp sử dụng là gì? Bạn có thực sự cần toàn bộ dữ liệu tệp trong một 'byte []' không? – Santosh

+0

Bạn có thể cho tôi biết bạn đang sử dụng phiên bản JDK nào không, tôi có giải pháp khác cho JDK 8 và JDK7 hoặc thấp hơn. – Bhupi

+0

@Làm thế nào để trả lời câu hỏi này mà không biết ** tại sao ** quá nhiều dữ liệu được đọc vào bộ nhớ? – k3b

Trả lời

7

Các máy ảo Java (JVM) chạy với một giới hạn bộ nhớ cố định trên, mà bạn có thể sửa đổi như sau:

java -Xmx1024m .... 

ví dụ tùy chọn ở trên (-Xmx ...) đặt giới hạn là 1024 megabyte. Bạn có thể sửa đổi khi cần thiết (trong giới hạn của máy, HĐH, v.v.) Lưu ý rằng điều này khác với các ứng dụng truyền thống sẽ phân bổ ngày càng nhiều bộ nhớ từ hệ điều hành theo yêu cầu.

Tuy nhiên, giải pháp tốt hơn là làm lại ứng dụng của bạn sao cho bạn không cần tải toàn bộ tệp toàn bộ vào bộ nhớ một lần. Bằng cách đó bạn không phải điều chỉnh JVM của bạn, và bạn không áp đặt một dấu chân bộ nhớ khổng lồ.

4

Bạn không thể đọc 10GB Textfile trong bộ nhớ. Bạn phải đọc X MB trước tiên, làm điều gì đó với nó và đọc X MB tiếp theo.

+3

Nếu anh ấy có 10Gb và một JVM 64bit, anh ấy * có thể * thực hiện việc này. Có lẽ anh ta không nên. –

+0

bất kỳ trợ giúp nào về đọc trong phân vùng? –

+0

@Brian Không, anh ấy không thể. Ngay cả dưới 64bit chỉ có một kích thước giới hạn của các phần tử trong một mảng có thể. – sigi

3

ByteArrayOutputStream ghi vào bộ đệm trong bộ nhớ. Nếu đây thực sự là cách bạn muốn nó hoạt động, thì bạn phải kích thước vùng heap JVM sau kích thước tối đa có thể của đầu vào. Ngoài ra, nếu có thể, bạn có thể kiểm tra kích thước đầu vào trước khi bắt đầu xử lý để tiết kiệm thời gian và tài nguyên.

Cách tiếp cận thay thế là giải pháp truyền trực tuyến, nơi mà lượng bộ nhớ được sử dụng trong thời gian chạy được biết (có thể cấu hình nhưng vẫn được biết trước khi chương trình bắt đầu), nhưng nếu khả thi hoặc không phụ thuộc hoàn toàn vào miền của ứng dụng của bạn (vì bạn có thể không sử dụng bộ đệm trong bộ nhớ nữa) và có thể là kiến ​​trúc của phần còn lại của mã nếu bạn không thể/không muốn thay đổi nó.

4

Thử sử dụng kích thước đọc bộ đệm lớn có thể là 10 mb và sau đó kiểm tra.

4

Vấn đề là cố hữu trong những gì bạn đang làm. Đọc toàn bộ tập tin vào bộ nhớ luôn luôn và ở khắp mọi nơi một ý tưởng tồi. Bạn thực sự sẽ không thể đọc một tập tin 10GB vào bộ nhớ với công nghệ hiện tại trừ khi bạn có một số phần cứng khá đáng ngạc nhiên. Tìm một cách để xử lý chúng theo từng dòng, ghi lại theo hồ sơ, chunk theo chunk, ...

+0

"Đọc toàn bộ tập tin vào bộ nhớ luôn luôn và ở khắp mọi nơi một ý tưởng tồi"? Hãy nói với biên tập viên của tôi! :-) –

17

Hãy thử sử dụng java.nio.MappedByteBuffer.

http://docs.oracle.com/javase/7/docs/api/java/nio/MappedByteBuffer.html

Bạn có thể lập bản đồ nội dung của tập tin vào bộ nhớ mà không cần sao chép nó bằng tay. Các hệ điều hành cấp cao cung cấp ánh xạ bộ nhớ và Java có API để sử dụng tính năng này.

Nếu hiểu biết của tôi là chính xác, ánh xạ bộ nhớ không tải toàn bộ nội dung của tệp vào bộ nhớ (nghĩa là "tải và tải một phần khi cần thiết"), vì vậy tôi đoán tệp 10 GB sẽ không ăn hết bộ nhớ của bạn.

4

Bắt buộc phải nhận toàn bộ số ByteArray() luồng đầu ra?

byte[] data = bArrStream.toByteArray(); 

Cách tiếp cận tốt nhất được đọc từng dòng & viết từng dòng một. Bạn có thể sử dụng BufferedReader hoặc Scanner để đọc các tệp lớn như bên dưới.

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)); 

    } 
} 

Bạn có thể thay System.out với ByteArrayOutputStream của bạn trong ví dụ trên.

hãy có một cái nhìn tại bên dưới bài viết để biết thêm chi tiết: Read Large File

Có xem xét liên quan SE câu hỏi:

Scanner vs. BufferedReader

11

Mặc dù bạn có thể tăng giới hạn bộ nhớ JVM, nó là không cần thiết và phân bổ một bộ nhớ khổng lồ như 10GB để xử lý một tệp quá mức cần thiết và tài nguyên tập trung.

Hiện tại bạn đang sử dụng "ByteArrayOutputStream" giữ bộ nhớ trong để giữ dữ liệu. Dòng này trong mã của bạn tiếp tục thêm phần cuối cùng của tệp đệm 2KB vào cuối bộ đệm này:

bArrStream.write(localbuffer, 0, i); 

bArrStream tiếp tục phát triển và cuối cùng bạn hết bộ nhớ.

Thay vào đó bạn nên sắp xếp lại thuật toán của bạn và xử lý các tập tin một cách trực tiếp:

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(); 
3

Hi Tôi giả định rằng bạn đang đọc file txt lớn và các dữ liệu được thiết lập từng dòng, dòng sử dụng bởi dòng cách tiếp cận đọc. Như tôi biết bạn có thể đọc lên đến 6GB có thể nhiều hơn. Tôi mạnh mẽ khuyên bạn nên thử phương pháp này.

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(); 

Refrence for the code fragment

3

Đọc tập lặp đi lặp lại linewise. Điều này sẽ làm giảm đáng kể mức tiêu thụ bộ nhớ. Hoặc bạn có thể sử dụng

FileUtils.lineIterator (theFile, "UTF-8");

do Apache Commons cung cấp.

FileInputStream inputStream = null; 
Scanner sc = null; 
try { 
inputStream = new FileInputStream(path); 
sc = new Scanner(inputStream, "UTF-8"); 
while (sc.hasNextLine()) { 
    String line = sc.nextLine(); 
    // System.out.println(line); 
} 
// note that Scanner suppresses exceptions 
if (sc.ioException() != null) { 
    throw sc.ioException(); 
} 
} finally { 
if (inputStream != null) { 
    inputStream.close(); 
} 
if (sc != null) { 
    sc.close(); 
} 

}

5

Run Java với các tùy chọn dòng lệnh -Xmx, thiết lập kích thước tối đa của heap.

See here for details..

+0

Liên kết này không hoạt động đối với tôi, bạn có thể đưa thông tin quan trọng vào đây ngoài liên kết không? – innoSPG

2

Bạn nên tăng kích thước heap như đã nêu trong câu trả lời sau đây:

Increase heap size in Java

Nhưng hãy nhớ rằng thời gian chạy Java và bạn mã mất một không gian cũng như để thêm một số bộ đệm đến mong muốn tối đa.

2

Câu trả lời ngắn,

mà không làm bất cứ điều gì, bạn có thể đẩy giới hạn hiện tại bằng hệ số 1,5. Nó có nghĩa là, nếu bạn có thể xử lý 800MB, bạn có thể xử lý 1200 MB. Nó cũng có nghĩa là nếu có một số mẹo với java -Xm .... bạn có thể di chuyển đến một điểm mà mã hiện tại của bạn có thể xử lý 7 GB, vấn đề của bạn được giải quyết, vì hệ số 1.5 sẽ đưa bạn đến 10.5GB, giả sử bạn có không gian đó trên hệ thống của mình và JVM có thể lấy nó.

Câu trả lời dài:

Lỗi này khá tự mô tả. Bạn nhấn giới hạn bộ nhớ thực tế trên cấu hình của bạn. Có rất nhiều suy đoán về giới hạn mà bạn có thể có với JVM, tôi không biết đủ về điều đó, vì tôi không thể tìm thấy bất kỳ thông tin chính thức nào. Tuy nhiên, bằng cách nào đó, bạn sẽ bị hạn chế bởi các ràng buộc như trao đổi có sẵn, sử dụng không gian địa chỉ hạt nhân, phân mảnh bộ nhớ, v.v.

Điều đang xảy ra là ByteArrayOutputStream đối tượng được tạo với bộ đệm mặc định là 32 nếu bạn làm không cung cấp bất kỳ kích thước (đây là trường hợp của bạn). Bất cứ khi nào bạn gọi phương thức write trên đối tượng, có một máy móc nội bộ được khởi động. Các openjdk implementation release 7u40-b43 mà dường như phù hợp hoàn hảo với đầu ra của lỗi của bạn, sử dụng một phương pháp nội bộ ensureCapacity để kiểm tra xem bộ đệm có đủ chỗ để đặt các byte bạn muốn viết. Nếu không có đủ chỗ, một phương pháp bên trong khác là grow được gọi để tăng kích thước của bộ đệm. Phương thức grow xác định kích thước phù hợp và gọi phương thức copyOf từ lớp Arrays để thực hiện công việc. Kích thước thích hợp của bộ đệm là kích thước tối đa giữa kích thước hiện tại và kích thước được đặt để giữ tất cả nội dung (nội dung hiện tại và nội dung mới cần ghi). Phương thức copyOf từ lớp Arrays (follow the link) cấp không gian cho bộ đệm mới, sao chép nội dung của bộ đệm cũ sang bộ đệm mới và trả về grow.

Sự cố của bạn xảy ra khi phân bổ không gian cho bộ đệm mới, Sau một số write, bạn đã đến một điểm khi bộ nhớ khả dụng bị cạn kiệt: java.lang.OutOfMemoryError: Java heap space.

Nếu chúng ta nhìn vào chi tiết, bạn đang đọc bởi khối 2048. Vì vậy,

  • viết đầu tiên của mình vào phát triển kích thước của bộ đệm 32-2048
  • cuộc gọi thứ hai của bạn sẽ tăng gấp đôi nó để 2 * 2048
  • cuộc gọi thứ ba của bạn sẽ mang đến 2^2 * 2048, bạn phải dành thời gian viết thêm hai lần nữa trước khi cần phân bổ.
  • sau đó 2^3 * 2048, bạn sẽ có thời gian cho 4 mores viết trước khi phân bổ lại.
  • tại một số điểm, bộ đệm của bạn sẽ có kích thước 2^18 * 2048 là 2^19 * 1024 hoặc 2^9 * 2^20 (512 MB)
  • rồi 2^19 * 2048 là 1024 MB hoặc 1 GB

Có điều gì đó không rõ ràng trong mô tả của bạn là bạn bằng cách nào đó có thể đọc tới 800MB nhưng không thể vượt ra ngoài. Bạn phải giải thích điều đó với tôi.

Tôi hy vọng giới hạn của bạn chính xác là 2 (hoặc đóng nếu chúng tôi sử dụng sức mạnh của 10 đơn vị một cách khác). Về vấn đề đó, tôi hy vọng bạn bắt đầu gặp sự cố ngay lập tức trên một trong các điều sau: 256MB, 512 MB, 1GB, 2GB, v.v.

Khi bạn đạt đến giới hạn đó, điều đó không có nghĩa là bạn đã hết bộ nhớ chỉ đơn giản có nghĩa là không thể phân bổ bộ đệm khác gấp đôi kích thước của bộ đệm mà bạn đã có. Quan sát này mở chỗ cho sự cải tiến trong công việc của bạn: tìm ra kích thước tối đa của bộ đệm mà bạn có thể phân bổ và dự trữ nó trả trước bằng cách gọi các nhà xây dựng phù hợp

ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(myMaxSize); 

Nó có lợi thế là giảm cấp phát bộ nhớ nền overhead điều đó xảy ra dưới mui xe để giữ cho bạn hạnh phúc. Bằng cách này, bạn sẽ có thể đi tới giới hạn 1.5 bạn có ngay bây giờ. Điều này đơn giản là vì lần cuối cùng bộ đệm được tăng lên, nó đã đi từ một nửa kích thước hiện tại đến kích thước hiện tại, và tại một số điểm bạn có cả bộ đệm hiện tại và bộ đệm cũ cùng nhau trong bộ nhớ. Nhưng bạn sẽ không thể vượt quá 3 lần giới hạn mà bạn đang có bây giờ. Lời giải thích hoàn toàn giống nhau.

Điều đó đã được nói, tôi không có bất kỳ đề xuất kỳ diệu nào để giải quyết vấn đề ngoài quá trình xử lý dữ liệu của bạn theo từng kích thước nhất định, một đoạn tại một thời điểm. Một cách tiếp cận tốt khác là sử dụng đề xuất của Takahiko Kawasaki và sử dụng MappedByteBuffer. Hãy nhớ rằng trong mọi trường hợp, bạn sẽ cần ít nhất 10 GB bộ nhớ vật lý hoặc bộ nhớ hoán đổi để có thể tải một tệp có dung lượng 10GB.

xem

0

Sau khi nghĩ về điều đó, tôi quyết định đặt câu trả lời thứ hai. Tôi xem xét những lợi thế và bất lợi của việc đưa câu trả lời thứ hai này, và những lợi thế là giá trị đi cho nó. Vì vậy, ở đây nó được.

Hầu hết các cân nhắc được đề xuất đều quên một thực tế nhất định: Có giới hạn tích hợp trong kích thước mảng (bao gồm ByteArrayOutputStream) mà bạn có thể có trong Java. Và giới hạn đó được quyết định bởi giá trị lớn nhất int là 2^31 - 1 (ít hơn 2Giga). Điều này có nghĩa là bạn chỉ có thể đọc tối đa 2 GB (-1 byte) và đặt nó vào một đơn ByteArrayOutputStream. Giới hạn thực sự có thể nhỏ hơn đối với kích thước mảng nếu VM muốn kiểm soát nhiều hơn.

Đề xuất của tôi là sử dụng ArrayList của byte[] thay vì chỉ một byte[] giữ toàn bộ nội dung của tệp. Và cũng loại bỏ các bước không cần thiết của việc đưa vào ByteArrayOutputStream trước khi đặt nó trong một mảng data cuối cùng. Dưới đây là ví dụ dựa trên mã ban đầu của bạn:

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 

Chỉ cần chạy chương trình của bạn sẽ hoạt động tốt trên hệ thống 64 bit có đủ bộ nhớ vật lý hoặc trao đổi. Bây giờ nếu bạn muốn tăng tốc nó để giúp VM kích thước chính xác vùng heap ngay từ đầu, hãy chạy với các tùy chọn -Xms-Xmx.Ví dụ: nếu bạn muốn một đống 12 GB để có thể xử lý tệp 10GB, hãy sử dụng java -Xms12288m -Xmx12288m YourApp