2010-06-04 35 views
34

Tôi đang ánh xạ một tệp ("sample.txt") vào bộ nhớ bằng cách sử dụng FileChannel.map() và sau đó đóng kênh bằng cách sử dụng fc.close(). Sau này khi tôi ghi được vào file sử dụng FileOutputStream, tôi nhận được lỗi sau:Làm thế nào để unmap một tập tin từ bộ nhớ ánh xạ bằng cách sử dụng FileChannel trong java?

java.io.FileNotFoundException: sample.txt (The requested operation cannot be per formed on a file with a user-mapped section open)

File f = new File("sample.txt"); 
RandomAccessFile raf = new RandomAccessFile(f,"rw"); 
FileChannel fc = raf.getChannel(); 
MappedByteBuffer mbf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); 
fc.close(); 
raf.close(); 

FileOutputStream fos = new FileOutputStream(f); 
fos.write(str.getBytes()); 
fos.close(); 

Tôi đoán này có thể là do tập tin được vẫn ánh xạ vào bộ nhớ ngay cả sau khi tôi đóng FileChannel. Tôi có đúng không ?. Nếu vậy, làm thế nào tôi có thể "unmap" các tập tin từ bộ nhớ? (Tôi không thể tìm thấy bất kỳ phương pháp cho điều này trong API). Cảm ơn.

Edit: Hình như nó (thêm một phương pháp unmap) đã được gửi như RFE để ánh nắng mặt trời một thời gian trở lại: http://bugs.sun.com/view_bug.do?bug_id=4724038

+1

Tôi cũng gặp vấn đề tương tự. Trong trường hợp của tôi, tôi đã cố gắng ánh xạ hàng ngàn tệp vào bộ nhớ và kết quả là 'OutOfMemoryError', mà tôi tin là kết quả của việc xử lý tệp hết. Không có công cụ để unmap dẫn đến hành vi không tiên đoán, mặc dù bản đồ sẽ cố gắng 'ngủ (100)' & 'System.gc()' trên 'OutOfMemoryError' - nó chỉ trì hoãn các co giật. –

Trả lời

7

Từ MappedByteBuffer javadoc:

A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.

Thử gọi System.gc()? Thậm chí đó chỉ là một gợi ý cho VM.

+0

Cảm ơn rất nhiều. Tôi đã nhận nó làm việc nhưng sẽ không dựa vào System.gc() mang lại kết quả bất ngờ trên chạy khác nhau? – learner135

+0

Yup. Nếu điều đó giải quyết được vấn đề của bạn, thì ít nhất bạn cũng biết vấn đề là gì. Bây giờ bạn có thể quyết định xem bạn có muốn làm điều gì đó điên rồ như vòng lặp gọi là 'System.gc()' hay xem xét lại việc triển khai của bạn hay không. –

+0

Tôi đã thử System.gc() sau khi xóa tất cả các tham chiếu, nhưng tôi vẫn gặp lỗi. –

1

Bộ nhớ được ánh xạ được sử dụng cho đến khi được bộ thu gom rác giải phóng.

Từ FileChannel docs

A mapping, once established, is not dependent upon the file channel that was used to create it. Closing the channel, in particular, has no effect upon the validity of the mapping.

Từ MappedByteBuffer java doc

A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.

Vì vậy, tôi sẽ đề nghị đảm bảo không có tài liệu tham khảo còn lại để bộ đệm byte ánh xạ và sau đó yêu cầu thu gom rác thải.

+1

Không có cách nào để buộc GC. Thậm chí không thông qua 'System.gc()'. – aioobe

+2

đó là lý do tại sao tôi yêu cầu – BenM

+0

Chỉ cần một bình luận về một tuyên bố được thực hiện ở đây "Không có cách nào để buộc một GC. Không, ngay cả thông qua System.gc()" Trên thực tế có. Nếu bạn chắc chắn rằng tất cả các luồng ứng dụng không hoạt động hoặc đang ngủ, System.gc() sẽ chạy GC mỗi lần, ít nhất là trên máy ảo của Oracle. –

35

Tiếp theo phương pháp tĩnh có thể được sử dụng:

public static void unmap(MappedByteBuffer buffer) 
{ 
    sun.misc.Cleaner cleaner = ((DirectBuffer) buffer).cleaner(); 
    cleaner.clean(); 
} 

Nhưng đây là giải pháp an toàn vì sau:
1) Dẫn đến thất bại nếu ai đó sử dụng MappedByteBuffer sau unmap
2) Nó dựa vào MappedByteBuffer chi tiết thực hiện

+1

Không nên sử dụng "giải pháp" này. Nó vừa không thể chuyển đổi vừa không an toàn. – kittylyst

+21

@kittylyst vấn đề không nên được giải quyết. Công ty không nên bán sản phẩm của mình. Nhà phát triển không được thanh toán. – stepancheg

+0

@stephancheg Xem câu trả lời của tôi bên dưới để biết cách thực sự (chủ yếu) giải quyết vấn đề này. – kittylyst

2

Để khắc phục lỗi này trong Java, tôi phải làm như sau, sẽ hoạt động tốt cho các tệp nhỏ và vừa:

// first open the file for random access 
    RandomAccessFile raf = new RandomAccessFile(file, "r"); 

    // extract a file channel 
    FileChannel channel = raf.getChannel(); 

    // you can memory-map a byte-buffer, but it keeps the file locked 
    //ByteBuffer buf = 
    //  channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 

    // or, since map locks the file... just read the whole file into memory 
    ByteBuffer buf = ByteBuffer.allocate((int)file.length()); 
    int read = channel.read(buf); 

    // .... do something with buf 

    channel.force(false); // doesn't help 
    channel.close();  // doesn't help 
    channel = null;  // doesn't help 
    buf = null;   // doesn't help 
    raf.close();   // try to make sure that this thing is closed!!!!! 
-2

Nếu đối tượng đệm tệp được ánh xạ có thể được đảm bảo đủ điều kiện thu thập rác, bạn không cần phải GC toàn bộ VM để bộ nhớ được ánh xạ của bộ đệm được giải phóng. Bạn có thể gọi System.runFinalization(). Điều này sẽ gọi phương thức finalize() trên đối tượng đệm tệp được ánh xạ (nếu nó không có tham chiếu đến nó trong các chuỗi ứng dụng của bạn) sẽ giải phóng bộ nhớ được ánh xạ.

+1

No. Thay vào đó hãy sử dụng các nguồn lực thử. Việc hoàn thành nên * không bao giờ * được sử dụng. – kittylyst

+0

Mỗi tài liệu, quyết toán là cách duy nhất để hiển thị bản đồ. Khác với các phương pháp không có giấy tờ dựa vào các lớp * mặt trời, đây là giải pháp thực tế duy nhất cho vấn đề. Try-with-resources chỉ đóng kênh mà OP đã làm. – Jules

+0

GC không cần phải chạy để xác định đối tượng nào đủ điều kiện để hoàn thành? Có lẽ tốt hơn để gọi GC và sau đó 'runFinalization'? –

-12

Giải pháp đúng ở đây là sử dụng tài nguyên thử.

Điều này cho phép tạo kênh & các tài nguyên khác được đưa vào một khối. Khi khối thoát, kênh & các tài nguyên khác bị mất & sau đó không thể được sử dụng (vì không có gì có tham chiếu đến chúng).

Ánh xạ bộ nhớ vẫn sẽ không được hoàn tác cho đến khi lần chạy GC tiếp theo, nhưng ít nhất không có bất kỳ tham chiếu nào đáng ngờ đến nó.

+0

Điều này không giải quyết được gì. Vấn đề không phải là với các tham chiếu lơ lửng, nhưng với bản đồ chính nó vẫn còn sống. – stepancheg

+1

Vấn đề là ánh xạ bộ nhớ làm cho tệp bị khóa ở cấp hệ điều hành, có nghĩa là không thể ghi vào nó bằng cách sử dụng các hoạt động được ánh xạ không bộ nhớ hoặc để xóa nó. Giải pháp được cho là không có gì để giải quyết vấn đề này. – Jules

15

[WinXP, SunJDK1.6] Tôi đã có một ByteBuffer được ánh xạ lấy từ filechannel. Sau khi đọc SO bài viết cuối cùng quản lý để gọi một sạch hơn thông qua sự phản ánh mà không có bất kỳ mặt trời. Không còn khóa tệp nữa.

FileInputStream fis = new FileInputStream(file); 
FileChannel fc = fis.getChannel(); 
ByteBuffer cb = null; 
try { 
    long size = fc.size(); 
    cb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size); 
    ...do the magic... 
finally { 
    try { fc.close(); } catch (Exception ex) { } 
    try { fis.close(); } catch (Exception ex) { } 
    closeDirectBuffer(cb); 
} 

private void closeDirectBuffer(ByteBuffer cb) { 
    if (cb==null || !cb.isDirect()) return; 

    // we could use this type cast and call functions without reflection code, 
    // but static import from sun.* package is risky for non-SUN virtual machine. 
    //try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { } 
    try { 
     Method cleaner = cb.getClass().getMethod("cleaner"); 
     cleaner.setAccessible(true); 
     Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean"); 
     clean.setAccessible(true); 
     clean.invoke(cleaner.invoke(cb)); 
    } catch(Exception ex) { } 
    cb = null; 
} 

Ý tưởng được lấy từ các bài đăng này.
* How to unmap a file from memory mapped using FileChannel in java?
* Examples of forcing freeing of native memory direct ByteBuffer has allocated, using sun.misc.Unsafe?
* https://github.com/elasticsearch/elasticsearch/blob/master/src/main/java/org/apache/lucene/store/bytebuffer/ByteBufferAllocator.java#L40

+0

Làm việc cho chúng tôi (JDK 7 64 bit, Red Hat). Nó tránh các cảnh báo biên dịch trên gói "mặt trời" (chúng tôi luôn sử dụng cùng một máy ảo Oracle trong môi trường được kiểm soát và được xác định) – xav

5

sun.misc.Cleaner javadoc nói:

General-purpose phantom-reference-based cleaners. Cleaners are a lightweight and more robust alternative to finalization. They are lightweight because they are not created by the VM and thus do not require a JNI upcall to be created, and because their cleanup code is invoked directly by the reference-handler thread rather than by the finalizer thread. They are more robust because they use phantom references, the weakest type of reference object, thereby avoiding the nasty ordering problems inherent to finalization. A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner. Cleaners may also be invoked directly; they are thread safe and ensure that they run their thunks at most once. Cleaners are not a replacement for finalization. They should be used only when the cleanup code is extremely simple and straightforward. Nontrivial cleaners are inadvisable since they risk blocking the reference-handler thread and delaying further cleanup and finalization.

Chạy System.gc() là giải pháp có thể chấp nhận nếu tổng bộ đệm của bạn có kích thước là nhỏ, nhưng nếu tôi đang lập bản đồ gigabyte tệp tôi sẽ cố gắng triển khai như sau:

((DirectBuffer) buffer).cleaner().clean() 

Nhưng! Đảm bảo bạn không truy cập bộ đệm đó sau khi làm sạch hoặc bạn sẽ kết thúc với:

A fatal error has been detected by the Java Runtime Environment: EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000002bcf700, pid=7592, tid=10184 JRE version: Java(TM) SE Runtime Environment (8.0_40-b25) (build 1.8.0_40-b25) Java VM: Java HotSpot(TM) 64-Bit Server VM (25.40-b25 mixed mode windows-amd64 compressed oops) Problematic frame: J 85 C2 java.nio.DirectByteBuffer.get(I)B (16 bytes) @ 0x0000000002bcf700 [0x0000000002bcf6c0+0x40] Failed to write core dump. Minidumps are not enabled by default on client versions of Windows An error report file with more information is saved as: C:\Users\?????\Programs\testApp\hs_err_pid7592.log Compiled method (c2) 42392 85 4 java.nio.DirectByteBuffer::get (16 bytes) total in heap [0x0000000002bcf590,0x0000000002bcf828] = 664 relocation [0x0000000002bcf6b0,0x0000000002bcf6c0] = 16 main code [0x0000000002bcf6c0,0x0000000002bcf760] = 160 stub code
[0x0000000002bcf760,0x0000000002bcf778] = 24 oops
[0x0000000002bcf778,0x0000000002bcf780] = 8 metadata
[0x0000000002bcf780,0x0000000002bcf798] = 24 scopes data
[0x0000000002bcf798,0x0000000002bcf7e0] = 72 scopes pcs
[0x0000000002bcf7e0,0x0000000002bcf820] = 64 dependencies
[0x0000000002bcf820,0x0000000002bcf828] = 8

Chúc may mắn!

1
public static void unMapBuffer(MappedByteBuffer buffer, Class channelClass) { 
    if (buffer == null) { 
     return; 
    } 

    try { 
     Method unmap = channelClass.getDeclaredMethod("unmap", MappedByteBuffer.class); 
     unmap.setAccessible(true); 
     unmap.invoke(channelClass, buffer); 
    } catch (NoSuchMethodException e) { 
     e.printStackTrace(); 
    } catch (IllegalAccessException e) { 
     e.printStackTrace(); 
    } catch (InvocationTargetException e) { 
     e.printStackTrace(); 
    } 
} 
+1

Trong khi đoạn mã này được chào đón và có thể cung cấp một số trợ giúp. một lời giải thích] (// meta.stackexchange.com/q/114762) của * how * nó giải quyết câu hỏi. Nếu không có điều đó, câu trả lời của bạn có giá trị giáo dục ít hơn nhiều - hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai, không chỉ là người hỏi bây giờ! Vui lòng [sửa] câu trả lời của bạn để thêm giải thích và đưa ra chỉ dẫn về những giới hạn và giả định được áp dụng. –

0

Thật thú vị khi thấy rất nhiều đề xuất để làm những gì mà Mục 7 trong 'Java hiệu quả' cụ thể nói không nên làm. Một phương thức chấm dứt giống như những gì @Whome đã làm và không có tham chiếu đến bộ đệm là những gì cần thiết. GC không thể bị ép buộc. Nhưng điều đó không ngăn các nhà phát triển thử. Một workaround tôi thấy được để sử dụng WeakReferences từ http://jan.baresovi.cz/dr/en/java#memoryMap

final MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size); 
.... 
final WeakReference<mappedbytebuffer> bufferWeakRef = new WeakReference<mappedbytebuffer>(bb); 
bb = null; 

final long startTime = System.currentTimeMillis(); 
while(null != bufferWeakRef.get()) { 
    if(System.currentTimeMillis() - startTime > 10) 
// give up 
    return; 
    System.gc(); 
    Thread.yield(); 
} 
0

tôi sẽ cố gắng JNI:

#ifdef _WIN32 
UnmapViewOfFile(env->GetDirectBufferAddress(buffer)); 
#else 
munmap(env->GetDirectBufferAddress(buffer), env->GetDirectBufferCapacity(buffer)); 
#endif 

Bao gồm các file: windows.h cho Windows, sys/mmap.h cho BSD, Linux, OSX.

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