2017-03-11 18 views
11

Tôi đang chạy vào một OOM khi đọc một số lượng lớn các đối tượng từ ObjectInputStream với readUnshared. MAT điểm tại bảng xử lý nội bộ của nó là thủ phạm, cũng như theo dõi ngăn xếp OOM (ở cuối bài đăng này). Bởi tất cả các tài khoản, điều này không nên xảy ra. Hơn nữa, có hay không OOM xảy ra phụ thuộc vào cách các đối tượng được viết trước đó.Bất ngờ OutOfMemoryError khi sử dụng ObjectInputStream # readUnshared()

Theo this write-up on the topic, readUnshared nên giải quyết vấn đề (như trái ngược với readObject) bằng cách không tạo ra các mục bảng xử lý trong quá trình đọc (mà ghi-up là cách tôi phát hiện writeUnsharedreadUnshared, mà tôi trước đây đã không nhận thấy).

Tuy nhiên, dường từ những quan sát của riêng tôi mà readObjectreadUnshared cư xử hệt, và liệu oom xảy ra hay không phụ thuộc vào nếu các đối tượng được viết với một reset() after each write (nó không vấn đề nếu writeObject vs writeUnshared là được sử dụng, như tôi đã từng nghĩ - tôi chỉ cảm thấy mệt mỏi khi lần đầu tiên chạy thử nghiệm). Đó là:

 
       writeObject writeObject+reset writeUnshared writeUnshared+reset 
readObject  OOM    OK    OOM     OK 
readUnshared  OOM    OK    OOM     OK 

Vì vậy, có hay không có bất kỳ tác readUnsharedthực có vẻ là hoàn toàn phụ thuộc vào cách thức đối tượng được viết. Điều này thật đáng ngạc nhiên và bất ngờ đối với tôi. Tôi đã dành một số thời gian truy tìm thông qua các readUnshared code path nhưng, và cấp nó đã muộn và tôi đã mệt mỏi, nó không rõ ràng cho tôi lý do tại sao nó vẫn sẽ được sử dụng không gian xử lý và tại sao nó sẽ phụ thuộc vào cách đối tượng đã được viết (tuy nhiên, Bây giờ tôi có một nghi can ban đầu mặc dù tôi chưa xác nhận, được mô tả bên dưới).

Từ tất cả các nghiên cứu của tôi về chủ đề cho đến thời điểm này, nó xuất hiện writeObject với readUnsharednên hoạt động.

Dưới đây là chương trình tôi đã thử nghiệm với:

import java.io.BufferedInputStream; 
import java.io.BufferedOutputStream; 
import java.io.EOFException; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.io.Serializable; 


public class OOMTest { 

    // This is the object we'll be reading and writing. 
    static class TestObject implements Serializable { 
     private static final long serialVersionUID = 1L; 
    } 

    static enum WriteMode { 
     NORMAL,  // writeObject 
     RESET,  // writeObject + reset each time 
     UNSHARED, // writeUnshared 
     UNSHARED_RESET // writeUnshared + reset each time 
    } 

    // Write a bunch of objects. 
    static void testWrite (WriteMode mode, String filename, int count) throws IOException { 
     ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filename))); 
     out.reset(); 
     for (int n = 0; n < count; ++ n) { 
      if (mode == WriteMode.NORMAL || mode == WriteMode.RESET) 
       out.writeObject(new TestObject()); 
      if (mode == WriteMode.UNSHARED || mode == WriteMode.UNSHARED_RESET) 
       out.writeUnshared(new TestObject()); 
      if (mode == WriteMode.RESET || mode == WriteMode.UNSHARED_RESET) 
       out.reset(); 
      if (n % 1000 == 0) 
       System.out.println(mode.toString() + ": " + n + " of " + count); 
     } 
     out.close(); 
    } 

    static enum ReadMode { 
     NORMAL,  // readObject 
     UNSHARED // readUnshared 
    } 

    // Read all the objects. 
    @SuppressWarnings("unused") 
    static void testRead (ReadMode mode, String filename) throws Exception { 
     ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(filename))); 
     int count = 0; 
     while (true) { 
      try { 
       TestObject o; 
       if (mode == ReadMode.NORMAL) 
        o = (TestObject)in.readObject(); 
       if (mode == ReadMode.UNSHARED) 
        o = (TestObject)in.readUnshared(); 
       // 
       if ((++ count) % 1000 == 0) 
        System.out.println(mode + " (read): " + count); 
      } catch (EOFException eof) { 
       break; 
      } 
     } 
     in.close(); 
    } 

    // Do the test. Comment/uncomment as appropriate. 
    public static void main (String[] args) throws Exception { 
     /* Note: For writes to succeed, VM heap size must be increased. 
     testWrite(WriteMode.NORMAL, "test-writeObject.dat", 30_000_000); 
     testWrite(WriteMode.RESET, "test-writeObject-with-reset.dat", 30_000_000); 
     testWrite(WriteMode.UNSHARED, "test-writeUnshared.dat", 30_000_000); 
     testWrite(WriteMode.UNSHARED_RESET, "test-writeUnshared-with-reset.dat", 30_000_000); 
     */ 
     /* Note: For read demonstration of OOM, use default heap size. */ 
     testRead(ReadMode.UNSHARED, "test-writeObject.dat"); // Edit this line for different tests. 
    } 

} 

bước để tái tạo vấn đề với chương trình:

  1. Chạy chương trình thử nghiệm với testWrite s không chú thích (và testRead không được gọi) với kích thước heap được thiết lập cao, vì vậy writeObject không dẫn đến OOM.
  2. Chạy chương trình thử nghiệm lần thứ hai với testRead không được chú giải (và testWrite không được gọi) với kích thước heap mặc định.

Để rõ ràng: Tôi không viết và đọc trong cùng một cá thể JVM. Viết của tôi xảy ra trong một chương trình riêng biệt từ lần đọc của tôi. Chương trình thử nghiệm ở trên có thể hơi gây nhầm lẫn ngay từ cái nhìn đầu tiên do thực tế là tôi đã nhồi nhét cả các bài kiểm tra viết và đọc vào cùng một nguồn.

Thật không may, tình huống thực tế là tôi có một tệp chứa nhiều đối tượng được viết bằng writeObject (không có reset), sẽ mất khá nhiều thời gian để tạo lại (theo thứ tự ngày) (và cả reset làm cho các tệp đầu ra lớn), vì vậy tôi muốn tránh điều đó nếu có thể.Mặt khác, hiện tại tôi không thể đọc tệp với readObject, ngay cả khi không gian heap được tăng lên đến mức tối đa có sẵn trên hệ thống của tôi.

Điều đáng chú ý là trong tình huống thực tế của tôi, tôi không cần bộ đệm được cung cấp bởi các luồng đối tượng xử lý bảng.

Vì vậy, câu hỏi của tôi là:

  1. Tất cả các nghiên cứu của tôi cho đến nay cho thấy không có mối liên hệ giữa hành vi readUnshared 's và làm thế nào các đối tượng được viết. Chuyện gì đang xảy ra ở đây?
  2. Có cách nào tôi có thể tránh OOM khi đọc, với dữ liệu được viết bằng writeObject và không reset?

Tôi không hoàn toàn chắc chắn lý do tại sao readUnshared không giải quyết được vấn đề ở đây.

Tôi hy vọng điều này là rõ ràng. Tôi đang chạy trống rỗng ở đây nên có thể đã gõ những từ lạ.


Từ comments trên một câu trả lời dưới đây:

Nếu bạn không gọi writeObject() trong trường hợp hiện tại của JVM bạn không nên tốn nhiều bộ nhớ bằng cách gọi readUnshared().

Tất cả các nghiên cứu của tôi cho thấy giống nhau, tuy nhiên, mức gây nhầm lẫn:

  • Đây là oom stack trace, chỉ vào readUnshared:

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at java.io.ObjectInputStream$HandleTable.grow(ObjectInputStream.java:3464) 
    at java.io.ObjectInputStream$HandleTable.assign(ObjectInputStream.java:3271) 
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1789) 
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350) 
    at java.io.ObjectInputStream.readUnshared(ObjectInputStream.java:460) 
    at OOMTest.testRead(OOMTest.java:40) 
    at OOMTest.main(OOMTest.java:54) 
    
  • Đây là một (video video of it happening được ghi lại trước khi chỉnh sửa chương trình thử nghiệm gần đây, video tương đương với ReadMode.UNSHAREDWriteMode.NORMAL trong chương trình thử nghiệm mới).

  • Dưới đây là some test data files, chứa 30.000 đối tượng (kích thước nén là một nhỏ 360 KB nhưng được cảnh báo nó mở rộng đến khổng lồ 2,34 GB). Có bốn tệp thử nghiệm ở đây, mỗi tệp được tạo với các kết hợp khác nhau của writeObject/writeUnsharedreset. Hành vi đọc chỉ phụ thuộc vào cách nó được viết và độc lập với readObject so với readUnshared. Lưu ý rằng các tệp dữ liệu writeObjectwriteUnshared là giống hệt byte-byte, tôi không thể quyết định xem điều này có đáng ngạc nhiên hay không.


Tôi đã nhìn chằm chằm vào ObjectInputStream đang from here. nghi ngờ hiện tại của tôi là this line, hiện diện trong 1.7 và 1.8:

ObjectStreamClass desc = readClassDesc(false); 

đâu mà boolean tham số là true cho không chia sẻ và false cho bình thường.Trong tất cả các trường hợp khác, cờ "không được chia sẻ" được truyền qua các cuộc gọi khác, nhưng trong trường hợp đó, nó được mã hóa cứng thành false, do đó khiến cho các chốt xử lý được thêm vào bảng xử lý khi đọc mô tả lớp cho các đối tượng được tuần tự hóa ngay cả khi sử dụng readUnshared. AFAICT, đây là chỉ sự xuất hiện của cờ không được chia sẻ không được chuyển sang các phương pháp khác, do đó tại sao tôi tập trung vào nó.

Điều này trái ngược với ví dụ: this line nơi cờ không được chia sẻ được chuyển đến readClassDesc. (Bạn có thể theo dõi đường dẫn cuộc gọi từ readUnshared đến cả hai dòng đó nếu có ai muốn đào sâu.)

Tuy nhiên, tôi chưa xác nhận rằng điều này là quan trọng hoặc lý do tại sao false được mã hóa cứng ở đó. Đây chỉ là bài hát hiện tại tôi đang xem xét điều này, nó có thể chứng minh vô nghĩa.

Ngoài ra, fwiw, ObjectInputStream có phương pháp riêng, clear, xóa bảng điều khiển. Tôi đã làm một thử nghiệm mà tôi gọi đó (qua sự phản chiếu) sau mỗi lần đọc, nhưng nó đã phá vỡ tất cả mọi thứ, vì vậy đó là một không-đi.

Trả lời

2

Tuy nhiên, có vẻ như rằng nếu các đối tượng được viết bằng writeObject() hơn writeUnshared(), sau đó readUnshared() không giảm sử dụng xử lý bảng.

Điều đó là chính xác. readUnshared() chỉ làm giảm việc sử dụng bảng xử lý do số readObject(). Nếu bạn đang sử dụng cùng một JVM đang sử dụng writeObject() thay vì writeUnshared(), hãy xử lý việc sử dụng bảng có thể quy cho writeObject() không được giảm bởi readUnshared().

+0

Hm. Khi bạn nói "trong cùng một JVM", bạn có nghĩa là cùng một ví dụ? Trong trường hợp của tôi, các đối tượng được viết bởi một chương trình riêng biệt, tôi không làm 'writeObject' và' readUnshared' trong cùng một lần chạy. Trong chương trình thử nghiệm, chỉ một trong các 'testWrite' và' testRead' sẽ không được chú ý và chạy tại bất kỳ thời điểm nào. Tệp dữ liệu được tuần tự hóa của tôi đã tồn tại trước khi chạy các lần đọc. –

+0

Nếu bạn không gọi 'writeObject()' trong trường hợp hiện tại của JVM, bạn không nên tiêu thụ bộ nhớ bằng cách gọi 'readUnshared()'. – EJP

+0

Đó là những gì tôi đang nói! Nó không nên xảy ra. :) Tuy nhiên, nó có. Tôi đã thêm một dấu vết ngăn xếp cho câu hỏi trỏ đến 'readUnshared' cũng như [bằng chứng video] (https://www.youtube.com/watch?v=7-ASZJEKsYI). –

Các vấn đề liên quan