论坛首页 Java企业应用论坛

慎用 MappedByteBuffer!

浏览 14609 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-12-21  

<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为啥要搞这种半截子工程,大家有什么其他解决方法么?

   发表时间:2008-12-21  
1.MappedByteBuffer 也有clean方法啊。不行么。
2.NIO出现这种错误是正常的,因为NIO的含义是non-blocking IO。所以关闭的方法和IO中的操作不一样。你始终要处理异步或者是多线程的问题。
请自己查看:
java.nio.channels.spi.AbstractInterruptibleChannel

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

3 请登录后投票
   发表时间: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);
}
4 请登录后投票
   发表时间:2008-12-21   最后修改:2008-12-21
MappedByteBuffer的cleaner方法不是公开的,必须使用上述的那种手段进行调用,这样有个问题就是,我们无法控制客户使用的JVM,因为很多Application Server的JVM都不是SUN的,例如IBM或BEA,使用SUN所独有的内部类和方法会导致兼容性问题。

begin 和 end 这两个方法我在AbstractInterruptibleChannel类中看到都是protected的,无法在程序中调用。兄弟能详细解释一下么?
0 请登录后投票
   发表时间:2008-12-21   最后修改:2008-12-21
把sun的包复制出来
rt.jar
然后自己精简一下
0 请登录后投票
   发表时间: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 请登录后投票
   发表时间:2008-12-21  
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038

大家看看吵上天了, 老外也抓狂呢。
0 请登录后投票
   发表时间: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());


0 请登录后投票
   发表时间:2008-12-21   最后修改:2008-12-21
看错了啊, 确实不应该这么写的。LZ要分段做。
0 请登录后投票
   发表时间:2008-12-21   最后修改:2008-12-21
这个东西,我狂抓了大半年了··为了删除文件,用后台线程去gc``(幸好该操作不频)
这种情况下看gc log的时候··会出冷汗
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics