UDN-企业互联网技术人气社区

板块导航

浏览  : 1108
回复  : 0

[讨论交流] 哪个更快:Java堆还是本地内存

[复制链接]
呵呵燕的头像 楼主
发表于 2016-11-21 22:14:00 | 显示全部楼层 |阅读模式
  使用Java的一个好处就是你可以不用亲自来管理内存的分配和释放。当你用new关键字来实例化一个对象时,它所需的内存会自动的在Java堆中分配。堆会被垃圾回收器进行管理,并且它会在对象超出作用域时进行内存回收。但是在JVM中有一个‘后门’可以让你访问不在堆中的本地内存(native memory)。在这篇文章中,我会给你演示一个对象是怎样以连续的字节码的方式在内存中进行存储,并且告诉你是应该怎样存储这些字节,是在Java堆中还是在本地内存中。最后我会就怎样从JVM中访问内存更快给一些结论:是用Java堆还是本地内存。

  使用Unsafe来分配和回收内存

  sun.misc.Unsafe可以让你在Java中分配和回收本地内存,就像C语言中的malloc和free。通过它分配的内存不在Java堆中,并且不受垃圾回收器的管理,因此在它被使用完的时候你需要自己来负责释放和回收。下面是我写的一个使用Unsafe来管理本地内存的一个工具类:

  1.   public class Direct implements Memory {

  2.   private static Unsafe unsafe;

  3.   private static boolean AVAILABLE = false;

  4.   static {

  5.   try {

  6.   Field field = Unsafe.class.getDeclaredField("theUnsafe");

  7.   field.setAccessible(true);

  8.   unsafe = (Unsafe)field.get(null);

  9.   AVAILABLE = true;

  10.   } catch(Exception e) {

  11.   // NOOP: throw exception later when allocating memory

  12.   }

  13.   }

  14.   public static boolean isAvailable() {

  15.   return AVAILABLE;

  16.   }

  17.   private static Direct INSTANCE = null;

  18.   public static Memory getInstance() {

  19.   if (INSTANCE == null) {

  20.   INSTANCE = new Direct();

  21.   }

  22.   return INSTANCE;

  23.   }

  24.   private Direct() {

  25.   }

  26.   @Override

  27.   public long alloc(long size) {

  28.   if (!AVAILABLE) {

  29.   throw new IllegalStateException("sun.misc.Unsafe is not accessible!");

  30.   }

  31.   return unsafe.allocateMemory(size);

  32.   }

  33.   @Override

  34.   public void free(long address) {

  35.   unsafe.freeMemory(address);

  36.   }

  37.   @Override

  38.   public final long getLong(long address) {

  39.   return unsafe.getLong(address);

  40.   }

  41.   @Override

  42.   public final void putLong(long address, long value) {

  43.   unsafe.putLong(address, value);

  44.   }

  45.   @Override

  46.   public final int getInt(long address) {

  47.   return unsafe.getInt(address);

  48.   }

  49.   @Override

  50.   public final void putInt(long address, int value) {

  51.   unsafe.putInt(address, value);

  52.   }

  53.   }
复制代码


  在本地内存中分配一个对象

  让我们来将下面的Java对象放到本地内存中:

  1.   public class SomeObject {

  2.   private long someLong;

  3.   private int someInt;

  4.   public long getSomeLong() {

  5.   return someLong;

  6.   }

  7.   public void setSomeLong(long someLong) {

  8.   this.someLong = someLong;

  9.   }

  10.   public int getSomeInt() {

  11.   return someInt;

  12.   }

  13.   public void setSomeInt(int someInt) {

  14.   this.someInt = someInt;

  15.   }

  16.   }
复制代码


  我们所做的仅仅是把对象的属性放入到Memory中:

  1.   public class SomeMemoryObject {

  2.   private final static int someLong_OFFSET = 0;

  3.   private final static int someInt_OFFSET = 8;

  4.   private final static int SIZE = 8 + 4; // one long + one int

  5.   private long address;

  6.   private final Memory memory;

  7.   public SomeMemoryObject(Memory memory) {

  8.   this.memory = memory;

  9.   this.address = memory.alloc(SIZE);

  10.   }

  11.   @Override

  12.   public void finalize() {

  13.   memory.free(address);

  14.   }

  15.   public final void setSomeLong(long someLong) {

  16.   memory.putLong(address + someLong_OFFSET, someLong);

  17.   }

  18.   public final long getSomeLong() {

  19.   return memory.getLong(address + someLong_OFFSET);

  20.   }

  21.   public final void setSomeInt(int someInt) {

  22.   memory.putInt(address + someInt_OFFSET, someInt);

  23.   }

  24.   public final int getSomeInt() {

  25.   return memory.getInt(address + someInt_OFFSET);

  26.   }

  27.   }
复制代码


  现在我们来看看对两个数组的读写性能:其中一个含有数百万的SomeObject对象,另外一个含有数百万的SomeMemoryObject对象。

  1.   // with JIT:

  2.   Number of Objects: 1,000 1,000,000 10,000,000 60,000,000

  3.   Heap Avg Write: 107 2.30 2.51 2.58

  4.   Native Avg Write: 305 6.65 5.94 5.26

  5.   Heap Avg Read: 61 0.31 0.28 0.28

  6.   Native Avg Read: 309 3.50 2.96 2.16

  7.   // without JIT: (-Xint)

  8.   Number of Objects: 1,000 1,000,000 10,000,000 60,000,000

  9.   Heap Avg Write: 104 107 105 102

  10.   Native Avg Write: 292 293 300 297

  11.   Heap Avg Read: 59 63 60 58

  12.   Native Avg Read: 297 298 302 299
复制代码


  结论:跨越JVM的屏障来读本地内存大约会比直接读Java堆中的内存慢10倍,而对于写操作会慢大约2倍。但是需要注意的是,由于每一个SomeMemoryObject对象所管理的本地内存空间都是独立的,因此读写操作都不是连续的。那么我们接下来就来对比下读写连续的内存空间的性能。

  访问一大块的连续内存空间

  这个测试分别在堆中和一大块连续本地内存中包含了相同的测试数据。然后我们来做多次的读写操作看看哪个更快。并且我们会做一些随机地址的访问来对比结果。

  1.   // with JIT and sequential access:

  2.   Number of Objects: 1,000 1,000,000 1,000,000,000

  3.   Heap Avg Write: 12 0.34 0.35

  4.   Native Avg Write: 102 0.71 0.69

  5.   Heap Avg Read: 12 0.29 0.28

  6.   Native Avg Read: 110 0.32 0.32

  7.   // without JIT and sequential access: (-Xint)

  8.   Number of Objects: 1,000 1,000,000 10,000,000

  9.   Heap Avg Write: 8 8 8

  10.   Native Avg Write: 91 92 94

  11.   Heap Avg Read: 10 10 10

  12.   Native Avg Read: 91 90 94

  13.   // with JIT and random access:

  14.   Number of Objects: 1,000 1,000,000 1,000,000,000

  15.   Heap Avg Write: 61 1.01 1.12

  16.   Native Avg Write: 151 0.89 0.90

  17.   Heap Avg Read: 59 0.89 0.92

  18.   Native Avg Read: 156 0.78 0.84

  19.   // without JIT and random access: (-Xint)

  20.   Number of Objects: 1,000 1,000,000 10,000,000

  21.   Heap Avg Write: 55 55 55

  22.   Native Avg Write: 141 142 140

  23.   Heap Avg Read: 55 55 55

  24.   Native Avg Read: 138 140 138
复制代码


  结论:在做连续访问的时候,Java堆内存通常都比本地内存要快。对于随机地址访问,堆内存仅仅比本地内存慢一点点,并且是针对大块连续数据的时候,而且没有慢很多。

  最后的结论

  在Java中使用本地内存有它的意义,比如当你要操作大块的数据时(>2G)并且不想使用垃圾回收器(GC)的时候。从延迟的角度来说,直接访问本地内存不会比访问Java堆快。这个结论其实是有道理的,因为跨越JVM屏障肯定是有开销的。这样的结论对使用本地还是堆的ByteBuffer同样适用。使用本地ByteBuffer的速度提升不在于访问这些内存,而是它可以直接与操作系统提供的本地IO进行操作。

原文作者:佚名  来源:开发者头条
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部