概述
背景
使用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堆外内存(自己管理内存)的一些方法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复