`
yipsilon
  • 浏览: 242238 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

慎用 MappedByteBuffer!

阅读更多

<script type="text/javascript"><!----></script>

最近使用MD5进行大文件验证,固使用NIO这种高效率的模式来进行文件映射:

FileInputStream in = new FileInputStream(file);
FileChannel ch = in.getChannel();
MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
messageDigest.update(byteBuffer);
String md5 = bufferToHex(messageDigest.digest());
ch.close();
in.close();

本来想如果文件md5与数据库存储的值不同就删掉该文件的,结果出现了文件无法删除的情况。

抛出的违例
java.io.FileNotFoundException: E:\hello.jar (请求的操作无法在使用用户映射区域打开的文件上执行。)

后来经查,原来是当使用 FileChannel.map 方法时,MappedByteBuffer 已经在系统内占用了一个句柄,而使用 FileChannel.close 方法是无法释放这个句柄的,且FileChannel有没有提供类似 unmap 的方法,因此会出现无法删除文件的情况。

 

此问题一直让我很郁闷,后来到网上查询到两种方法...

 

第一种:

AccessController.doPrivileged(new PrivilegedAction() {
  public Object run() {
    try {
      Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);
      getCleanerMethod.setAccessible(true);
      sun.misc.Cleaner cleaner = (sun.misc.Cleaner) 
      getCleanerMethod.invoke(byteBuffer, new Object[0]);
      cleaner.clean();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }
});

此种方法需要JDK支持,我用的是JRE 1.6,提示没有 sun.misc.Cleaner 类,JDK包太大,项目又不让用。

 

第二种方法是显性设置byteBuffer为null,并调用GC,没什么实际意义。

 

实在没招了,又回来使用InputStream了...

FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream((int)file.length());
byte[] cache = new byte[1048576];
for(int i = in.read(cache);i != -1;i = in.read(cache)){
  out.write(cache, 0, i);
}
in.close();
out.close();
messageDigest.update(out.toByteArray());
md5 = bufferToHex(messageDigest.digest());

不知道SUN为啥要搞这种半截子工程,大家有什么其他解决方法么?

分享到:
评论
19 楼 dsjt 2013-05-29  
map是映射到直接内存,回收比较复杂。
用堆内存缓存:
ByteBuffer buffer= ByteBuffer.allocate(1024*1024);
FileChannel.read(buffer);


18 楼 sonic39 2009-02-21  
小弟过来学习中
17 楼 d2lorder 2008-12-23  
哈哈  我也遇到过
但是使用 第一种方法就可以搞定了
第二种方法 也用过,也可以实现
16 楼 AreYouOK? 2008-12-23  

引用
我觉得mappedbytebuffer更适用于文件较小,但是有些字节需要反复读取得情况


大约是这样的,对于计算一个大文件的MD5来说,每个字节都只读取一次,映射到内存应该是不会快的。

对于特别小的文件,javadoc是这么说的:
引用
For most operating systems, mapping a file into memory is more expensive than reading or writing a few tens of kilobytes of data via the usual read and write methods. From the standpoint of performance it is generally only worth mapping relatively large files into memory.

也就是说,它建议把“大”文件做映射(实际上它说的也就是要读写几十K),但是我想也许这个映射的开销是一次性的,对于小文件以后反复读写就快了吧。

这个映射的特点是可以将超过(物理内存+虚拟内存)、超过JVM最大内存大小的文件部分装入内存。因此,所谓装入内存实际上是虚拟的,我们看到的就是一个大数组,读的时候,如果在内存里面,就从内存里面读,否则就要读文件。

由于映射是操作系统负责的,所以其很多行为都是不确定的,在javadoc里面就有很多这方面的描述。主要是写操作后,数据对其它程序的可见性。

另外还有一个问题,就是map的文件部分的大小也是有限制的。如下:

WinXP 32位 2G内存,2G虚拟内存,Map一个2G的文件,XmX=64M
map 0~500M  OK
map 0~1G      内存溢出错误
循环,每次map1M,返回的buffer不释放,到达将近2G时内存溢出,也就是说,map的文件部分的总和也是有限制的

Linux 32位 2G内存,1G虚拟内存,Map一个3G多的文件,XmX=64M
map 0~3G  IllegalArgumentException,map的长度不能超过Integer.MAX_VALUE,也就是不能超过2G
map 0~Integer.MAX_VALUE ok
循环,每次map1M,返回的buffer不释放,到达2G后(2G或者2G多一点?)时内存溢出

Linux 64位 2G内存,2G虚拟内存,Map一个5G的文件,XmX=64M
map 0~5G  IllegalArgumentException,map的长度不能超过Integer.MAX_VALUE,也就是不能超过2G
map 0~Integer.MAX_VALUE ok
循环,每次map1M,返回的buffer不释放,没有发现限制


有意思的是,第三个参数的类型是long,但是却不允许超过Interger.MAX_VALUE,不知是在其它操作系统下可以,还是给将来留一点余地?
反正现在在Linux64下,是基本够用了。




15 楼 bloodrate 2008-12-23  
猜想这样,FileInputStream 的 read 方法是通过与本地文件的通道中从文件读取数据,由于只能知道当前读取的内容,未读取的内容在硬盘中处于未知状态,所以FileInputStream没有length方法(刚学io的时候觉得很蹩脚),想读第2个字节必选先读第1个字节.而用内存地址映射将整个文件存储到内存中,一切都变成可控可操作的,但是内容中对象确实要交由jvm处理,这点java机制所致没办法,至于为什么占着本地文件引用不释放就不理解了,按说文件被加载道内存里后就与本地文件划清界限了..

我觉得mappedbytebuffer更适用于文件较小,但是有些字节需要反复读取得情况
14 楼 seen 2008-12-23  
期待yywill出来解释。。。
13 楼 kusix 2008-12-22  
一年前就碰到过这个问题,当时也是被恶心到了,SUN的一个BUG。

我是用的第一种方法解决的。
12 楼 AreYouOK? 2008-12-22  
我也遇到这个问题,它还有其他一些很不确定的行为,例如:
引用
On some systems, acquiring a mandatory lock on a region of a file prevents that region from being mapped into memory, and vice versa. Programs that combine locking and mapping should be prepared for this combination to fail.


引用
MappedByteBuffer是java平台共享内存的实现,把硬盘虚拟为内存,主要用于进程间共享数据,所以在进程没有退出前文件是不允许删除的。

不是这样的,看javadoc,不知道为什么有这么恶心的设计:
引用
A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.
11 楼 gqf2008 2008-12-22  
MappedByteBuffer是java平台共享内存的实现,把硬盘虚拟为内存,主要用于进程间共享数据,所以在进程没有退出前文件是不允许删除的。
10 楼 yipsilon 2008-12-22  
通过 codeutil 的代码修正了问题,感谢!!!!

除非SUN把这个给解决,不然再也不想用ByteBuffer这东西了,哎哎。
9 楼 gembler 2008-12-21  
这个东西,我狂抓了大半年了··为了删除文件,用后台线程去gc``(幸好该操作不频)
这种情况下看gc log的时候··会出冷汗
8 楼 sdh5724 2008-12-21  
看错了啊, 确实不应该这么写的。LZ要分段做。
7 楼 codeutil 2008-12-21  

不说MappedByteBuffer的问题,楼主你如此hash有点不妥,如果是22G的文件你的写法都得全读到内存才计算hash。



    public static String getHash(String fileName, String hashType) throws  
            Exception {  
        InputStream fis;  
        fis = new FileInputStream(fileName);  
        byte[] buffer = new byte[1024];  
        MessageDigest md5 = MessageDigest.getInstance(hashType);  
        int numRead = 0;  
        while ((numRead = fis.read(buffer)) > 0) {  
            md5.update(buffer, 0, numRead);  
        }  
        fis.close();  
        return toHexString(md5.digest());  
    } 


对比:

FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream((int)file.length());
byte[] cache = new byte[1048576];
for(int i = in.read(cache);i != -1;i = in.read(cache)){
  out.write(cache, 0, i);
}
in.close();
out.close();
messageDigest.update(out.toByteArray());
md5 = bufferToHex(messageDigest.digest());


6 楼 sdh5724 2008-12-21  
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038

大家看看吵上天了, 老外也抓狂呢。
5 楼 sdh5724 2008-12-21  
楼住, 你发现的问题是真的。 你是发现了SUN的一个很严重的设计问题。 我也不知道SUN什么时候会解决, 在mmap中, SUN这个老顽固, 认为Java的内存管理就应该是jvm干的事情, 因此, 在mmap使用上, Sun依然认为mmap是jvm应该管理的内存资源。 在很多设计者的眼里, mmap资源应该属于一种特殊的内存资源, 而且他还是属于一种特殊的文件资源, 他根本不能使用jvm的内存管理办法来管理这个资源。当前mmap对象被JVM创建(ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());)后, 如果想释放, 就要等到GC干活的时候。 这个是非常恶心的设计。

另外有人把io/nio就是看作阻塞, 非阻塞的关系, 或者是同步与异步处理是不对的。nio的原理更应该理解为操作系统对资源一个处理方式,通过内核事件来通知资源就绪的情况, 这个处理模式极大的提高了系统的高并发处理能力和CPU利用效率, 仅此而已。

到目前为止, 没有好的解决办法(除了HACKING)
4 楼 yywill 2008-12-21  
把sun的包复制出来
rt.jar
然后自己精简一下
3 楼 yipsilon 2008-12-21  
MappedByteBuffer的cleaner方法不是公开的,必须使用上述的那种手段进行调用,这样有个问题就是,我们无法控制客户使用的JVM,因为很多Application Server的JVM都不是SUN的,例如IBM或BEA,使用SUN所独有的内部类和方法会导致兼容性问题。

begin 和 end 这两个方法我在AbstractInterruptibleChannel类中看到都是protected的,无法在程序中调用。兄弟能详细解释一下么?
2 楼 yywill 2008-12-21  
请用begin和end标记你的map操作,告诉jvm你的map什么时候结束

boolean completed = false;
try {
     begin();
     completed = ...;    // Perform blocking I/O operation
return ...;         // Return result
} finally {
     end(completed);
}
1 楼 yywill 2008-12-21  
1.MappedByteBuffer 也有clean方法啊。不行么。
2.NIO出现这种错误是正常的,因为NIO的含义是non-blocking IO。所以关闭的方法和IO中的操作不一样。你始终要处理异步或者是多线程的问题。
请自己查看:
java.nio.channels.spi.AbstractInterruptibleChannel

J2SE没有半截子工程。不要用IO的方法来操作NIO的类,当你用到NIO就要始终处理多线程,处理异步。

相关推荐

    深入浅出MappedByteBuffer.pdf

    深入浅出MappedByteBuffer

    Bug ID 4724038 (fs) Add unmap method to MappedByteBuffer

    Bug ID 4724038 (fs) Add unmap method to MappedByteBuffer

    文件分割和合并(您的文件大的不能传输怎么办?)

    本人初学c++,写了一个小软件,能把大文件分割问小文件,然后可以统国网络传输,到了网络另一端,再用此软件拼接! 希望用过的人能提宝贵意见! 13521825644 qq 362192302

    读取文件数据并解析成bean实体类

    很多时候需要文件做数据交互,接收到文件后需要对文件解析成bean实体类,这里提供的是工具类,任意文件转任意实体都可以,只要简单的配置一下Class类,很实用

    j2se项目源码及介绍_last指令

    函数原型 private MappedByteBuffer mappedFile2Buffer(File f) throws Exception 函数说明 把日志文件映射成内存缓冲 参数说明 @param File f日志文件 返回说明 @return MappedByteBuffer 内存映射缓冲。 异常说明 ...

    commons-mmf.rar_java nio_java共享内存_共享内存

    java的共享内存管理.基于MMF设计。封装了java.nio.MappedByteBuffer.在大流量实时业务系统时,可以极大的提高处理效率

    Android代码-BitMap

    a pratise of bigdata sorting,use some common util or class,like File,FileOutputStream,RandomAccessFile,HashMap,BufferedOutputStream,ByteBuffer,MappedByteBuffer,FileInputStream. as a newer of ...

    java8源码-netty-learn:这是一个用于netty学习的工程

    (用于TCP网络编程,客户端和服务器端都能用) ServerSocketChannel (用于TCP网络编程,专用与服务器端) 常见的Buffer,用来缓冲读写数据。 ByteBuffer MappedByteBuffer DirectByteBuffer HeapByteBuffer ...

    【密码:5261】Oracle官网下载64位-JDK14

    jdk14新特性:改进NullPointerExceptions,通过准确描述哪些变量为null...非易失性映射的字节缓冲将添加新的JDK特定文件映射模式,该模式允许FileChannel API用于创建引用非易失性内存(NVM)的MappedByteBuffer实例。

    jdk-14_linux-x64_bin.rpm

    非易失性映射的字节缓冲将添加新的 JDK 特定文件映射模式,该模式允许 FileChannel API 用于创建引用非易失性内存(NVM)的 MappedByteBuffer 实例。 358:Helpful NullPointerExceptions 改进 ...

    txt文档阅读器

    //用第二个参数的字符串替换第一个 strParagraph = strParagraph.replaceAll("\n", ""); //替换掉任何过行的记录 if (strParagraph.length() == 0) {//为空,没有字符串时 paraLines.add...

    CsvReader:CsvReader

    the performance improvement is not so much as against using MappedByteBuffer. -&gt; Otherwise I will totally use MappedByteBuffer 我无法在symbol.txt上进行验证。 单元测试:我找不到足够的时间来运行所有...

    ip地址库 很全的库

    import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.regex.Matcher; import java...

    mmfinvoker:简单的进程间 java 请求-响应库

    mmfinvoker 这是一个简单的 java 库,它使用 nio.MappedByteBuffer 在内存映射文件上实现请求/响应功能。

    javaredis源码-jredis-master:java实现redis

    java redis源码杰里迪斯 Java的redis实现,与redis服务器相同。 特征 ...MappedByteBuffer 2.fixed unit size ,head 4 byte write in last item position, and then each item write in 4 byte with i

    mmf4j:MemoryMappedFiles4Java

    与已经存在的MappedByteBuffer相比,目标是更好地控制创建,修改和销毁。 它试图统一在不同操作系统上使用此类映射的接口,这意味着许多细节无法实现。 此外,在某些情况下,您可能需要完成目标平台上可能不需要的...

    sambox:一个PDFBox分支,打算用作Sejda和PDFsam的PDF处理程序

    SAMBox使用允许使用基于java.nio.channels.FileChannel , java.io.InputStream和java.nio.MappedByteBuffer的提供的实现之一(是否缓冲)。 通过使用java.lang.StringBuilder池最小化GC。 通过绑定视图的概念直接...

    百度地图开发java源码-inertiaSearch:挑战赛

    百度地图开发java源码 tmp #inertiaSearch 2016年写的代码,现在觉得思路有很多提升的地方,但是毕竟...项目大量的使用了nio中的mmap(MappedByteBuffer) 对于原始的数据文件做内存映射,并做对应索引,所有索引做hash

    Android渠道打包工具packer-ng-plugin.zip

    }读取ZIP文件注释,有两个版本的实现,这里使用的是 RandomAccessFile ,另一个版本使用的是 MappedByteBuffer ,经过测试,对于特别长的注释,使用内存映射文件读取性能要稍微好一些,对于特别短的注释(比如渠道名...

Global site tag (gtag.js) - Google Analytics