我是靠谱客的博主 土豪黑米,最近开发中收集的这篇文章主要介绍使用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了,老大还不让加机器,呵呵),而且业务相对简单,那么也许可以尝试一下。

最后

以上就是土豪黑米为你收集整理的使用Java堆外内存(自己管理内存)的一些方法的全部内容,希望文章能够帮你解决使用Java堆外内存(自己管理内存)的一些方法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(68)

评论列表共有 0 条评论

立即
投稿
返回
顶部