使用Java堆外内存(自己管理内存)的一些方法

背景

      使用Java的一大好处就是自动内存管理,程序员不用太关心内存的是否,JVM的Garbage Collector(GC)帮我们找到不被引用的垃圾对象并清除掉。但是有得必有失,我们也失去了自己管理内存的可能性。【个人观点:大多数程序员的内存管理水平都比不上JVM,虽然程序员可能更了解业务逻辑,知道某个对象已经死掉,可以回收。但是回收的内存什么时候应该compact,内存分配使用什么算法,这样不是简单的问题。所以如果不是逼不得已,最好的办法还是优化代码,优化JVM的GC参数】

      但是可能还是存在一些应用场景,如果能够自己管理内存的话,性能可能更优(或者说代码写得好的话可能更优)。比如一些非常简单的业务逻辑或者算法,需要使用大量内存,尤其是像Cache,会在内存里呆很久。但是如果放到堆里,那么会影响Full GC的Pause Time

问题

      但是Java的设计者并不允许我们这么做(可能设计者的理念是:一旦留了个后门,那么就会有各种“优化秘笈”,各种水平的程序员都会附庸风雅一番,结果导致更差的性能,然后就会得出结论:Java语言很差。这就像我们印象中的Firefox很慢,其实很大一部分原因可能是我们使用了憋足的插件,但是我们不会管那么多,我们只会把罪名归到Firefox身上)       

救星------ByteBuffer

      在NIO出现之前应该没有什么办法可以自己申请堆外内存,但是NIO为了高效,“不得不”提供了ByteBuffer。但是它还是把ByteBuffer纳入了管理的范畴,你没有办法显示“释放”ByteBuffer,就像你调用System.gc()不能保证调用GC一样,你只能祈祷上天保佑马上会有GC。不过如果不考虑JVM的移植和版本的升级,Sun的DirectBuffer接口提供了Cleaner对象(sun.misc.Cleaner)。这是有风险的,比如你不使用Sun或者OpenJDK,或者等到以后的某个JDK版本,这些方法可能木有了。我们使用的ByteBuffer.allocateDirect得到的就是DirectByteBuffer,它实现了DirectBuffer接口。

等等------问题还没解决

      给我一个ByteBuffer有什么用?我可以把它变成一个byte[]。那照样没有什么用,难道我必须这样修改代码?

class Person{
    long id;
    int age;
    boolean gender;

    public int getAge(){
       return age;
    }
    public void setAge(int age){
       this.age=age;
    }
    .....
}

class PersonWrapper{
    ByteBuffer buf;

    public int getAge(){//假设我们的内存布局是上面的顺序
        return buf.getInt(8);
    }
    public PersonWrapper(){
        buf=ByteBuffer.allocateDirect(13);
    }

    public free(){
        ((DirectBuffer) buffer).cleaner().clean();
    }
}

     这样的代码看起来很像c的malloc和free,但是尽量别这么用。这里有个问题就是我们只能使用原始类型,如果你使用了对象,那么还是在Heap里由GC管理。

     如果你真的这么写Java代码那么我建议你直接使用C/C++。我们使用Java很大一部分是使用Java的类库,包括JRE里语言规定的和各种第三方的。我们的代码需要使用它们提供的对象。我们需要传递参数给它,这些参数可能就是对象,返回值也是。

     当然如果你从头开始必须用Java实现一个基础模块,它不需要使用任何其它对象,那么也许你可以考虑这种方法。这种方法的对象回收有两种方式,一种就是显示调用free方法,像我们写c一样,另外就是不管它,等PersonWrapper被回收的适合,buf也自动变成垃圾了。但正如GC的不确定性,可能等到GC再回收的话,可能Heap+DirectMemory把内存都用爆了,甚至连JVM都没有内存做GC!!

      我们可能更喜欢使用Java的对象编码(虽然使用Wrapper的方法可能更节省空间),字节数组实在太难用了。解决这个问题有下面两种方法:

序列化/反序列化

      对于Cache这样的应用,我们可以把它存到堆外,写的时候把对象序列化成byte[],保存至DirectBuffer,使用时把byte[] 反序列化回来。这个方法的缺点是序列化和反序列化会消耗CPU,另外如果对象很大的话,使用时反序列化出来的对象还是会占用Heap空间。

      这种方法可以参考或者使用http://incubator.apache.org/directmemory/  这是一个Apache的孵化项目,使用风险自负,呵呵。

      淘宝的一些测试:http://rdc.taobao.com/team/jm/archives/1539

字节码修改

      和前面的wrapper类似,不过提供更友好的方式,自己少些那么多代码,不用记住某个字段的指针偏移。

      参考项目:https://code.google.com/p/vanilla-java/wiki/HugeCollections

      缺点,和wrapper一样,只能使用原生类型,如果使用对象,那么这个对象还是在堆里。因为我们不可能写所有的代码,比如要使用别人写好的对象。

      可能的解决办法和序列化类似,把需要用的对象拷贝到ByteBuffer,就像C++的拷贝构造函数,而不是传递指针或者引用。

有没有更好的解决办法?

      或许有,比如想淘宝他们自己修改定制JVM,当然这就完全修改了Java的语法,或者不能叫Java语言了。

      有兴趣的可以看看http://www.infoq.com/cn/presentations/ms-jvm-taobao 作者的博客:http://rednaxelafx.iteye.com/

结论 
      在某些特殊场景(比如没有任何办法能够优化GC的Pause Time了,老大还不让加机器,呵呵),而且业务相对简单,那么也许可以尝试一下。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java内存泄露是指在使用Java NIO进行堆内存分配时,由于程序逻辑或代码问题导致堆内存没有得到正确的释放,从而导致占用的内存越来越多,最终导致内存泄露。 下面是一些可能导致Java内存泄露的原因和解决方案: 1. 内存分配和释放不匹配。在使用Java NIO进行堆内存分配时,需要及时释放内存,否则会导致内存泄露。解决方法是确保内存分配和释放匹配,特别是在使用DirectByteBuffer分配堆内存时,需要显式地调用ByteBuffer的clear()或compact()方法释放内存。 2. 使用ByteBuffer时没有适当的缓冲区管理。当使用ByteBuffer时,需要及时刷新缓冲区,否则会导致内存泄露。解决方法是及时刷新缓冲区,例如使用ByteBuffer的flip()方法刷新缓冲区。 3. 内存泄露分析工具不够好。目前有很多内存泄露分析工具,但是并不是所有工具都能准确地检测Java内存泄露。解决方法是选择一款专门针对Java内存泄露的工具进行分析。 4. 不合理的内存分配策略。在使用Java NIO进行堆内存分配时,需要注意内存分配策略,例如一次分配太多内存可能会导致内存泄露。解决方法是根据实际情况选择合适的内存分配策略,例如分段分配内存等。 希望这些解决方案能帮助您解决问题。如果仍然存在问题,请提供更多详细信息,以便更好地了解问题并提供更好的建议。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值