Java中内存分配的几种方法
一、数组分配的上限 Java里数组的大小是受限制的,因为它使用的是int类型作为数组下标。这意味着你无法申请超过Integer.MAX_VALUE(2^31-1)大小的数组。这并不是说你申请内存的上限就是2G。你可以申请一个大一点的类型的数组。比如: 复制代码 代码如下: final long[] ar = new long[ Integer.MAX_VALUE ]; 这个会分配16G -8字节,如果你设置的-Xmx参数足够大的话(通常你的堆至少得保留50%以上的空间,也就是说分配16G的内存,你得设置成-Xmx24G。这只是一般的规则,具体分配多大要看实际情况)。 不幸的是,在Java里,由于数组元素的类型的限制,你操作起内存来会比较麻烦。在操作数组方面,ByteBuffer应该是最有用的一个类了,它提供了读写不同的Java类型的方法。它的缺点是,目标数组类型必须是byte[],也就是说你分配的内存缓存最大只能是2G。 二、把所有数组都当作byte数组来进行操作 假设现在2G内存对我们来说远远不够,如果是16G的话还算可以。我们已经分配了一个long[],不过我们希望把它当作byte数组来进行操作。在Java里我们得求助下C程序员的好帮手了――sun.misc.Unsafe。这个类有两组方法:getN(object,offset),这个方法是要从object偏移量为offset的位置获取一个指定类型的值并返回它,N在这里就是代表着那个要返回值的类型,而putN(Object,offset,value)方法就是要把一个值写到Object的offset的那个位置。 不幸的是,这些方法只能获取或者设置某个类型的值。如果你从数组里拷贝数据,你还需要unsafe的另一个方法,copyMemory(srcObject,srcOffset,destObject,destOffet,count)。这和System.arraycopy的工作方式类似,不过它拷贝的是字节而不是数组元素。 想通过sun.misc.Unsafe来访问数组的数据,你需要两个东西: 1.数组对象里数据的偏移量 我们来写个简单的例子吧。我们分配一个long数组,然后更新它里面的几个字节。我们把最后一个元素更新成-1(16进制的话是0xFFFF FFFF FFFF FFFF),然再逐个清除这个元素的所有字节。 复制代码 代码如下: final long[] ar = new long[ 1000 ]; final int index = ar.length - 1; ar[ index ] = -1; //FFFF FFFF FFFF FFFF System.out.println( "Before change = " + Long.toHexString( ar[ index ] )); for ( long i = 0; i < 8; ++i ) 想运行上面 这个例子的话,得在你的测试类里加上下面的静态代码块: 复制代码 代码如下: private static final Unsafe unsafe; static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe)field.get(null); } catch (Exception e) { throw new RuntimeException(e); } } private static final long longArrayOffset = unsafe.arrayBaSEOffset(long[].class); 复制代码 代码如下: Before change = ffffffffffffffff After change: i = 0,val = ffffffffffffff00 After change: i = 1,val = ffffffffffff0000 After change: i = 2,val = ffffffffff000000 After change: i = 3,val = ffffffff00000000 After change: i = 4,val = ffffff0000000000 After change: i = 5,val = ffff000000000000 After change: i = 6,val = ff00000000000000 After change: i = 7,val = 0 三、sun.misc.Unsafe的内存分配 上面也说过了,在纯Java里我们的能分配的内存大小是有限的。这个限制在Java的最初版本里就已经定下来了,那个时候人们都不敢相像分配好几个G的内存是什么情况。不过现在已经是大数据的时代了,我们需要更多的内存。在Java里,想获取更多的内存有两个方法:
sun.misc.Unsafe提供了一组方法来进行内存的分配,重新分配,以及释放。它们和C的malloc/free方法很像: 1.long Unsafe.allocateMemory(long size)――分配一块内存空间。这块内存可能会包含垃圾数据(没有自动清零)。如果分配失败的话会抛一个java.lang.OutOfMemoryError的异常。它会返回一个非零的内存地址(看下面的描述)。 这些方法分配的内存应该在一个被称为单寄存器地址的模式下使用:Unsafe提供了一组只接受一个地址参数的方法(不像双寄存器模式,它们需要一个Object还有一个偏移量offset)。通过这种方式分配的内存可以比你在-Xmx的Java参数里配置的还要大。 注意:Unsafe分配出来的内存是无法进行垃圾回收的。你得把它当成一种正常的资源,自己去进行管理。 下面是使用Unsafe.allocateMemory分配内存的一个例子,同时它还检查了整个内存缓冲区是不是可读写的: 复制代码 代码如下: final int size = Integer.MAX_VALUE / 2; final long addr = unsafe.allocateMemory( size ); try { System.out.println( "Unsafe address = " + addr ); for ( int i = 0; i < size; ++i ) { unsafe.putByte( addr + i,(byte) 123); if ( unsafe.getByte( addr + i ) != 123 ) System.out.println( "Failed at offset = " + i ); } } finally { unsafe.freeMemory( addr ); } 正如你所看见的,使用sun.misc.Unsafe你可以写出非常通用的内存访问的代码:不管是Java里分配的何种内存,你都可以随意读写任意类型的数据。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- 详解Java使用Pipeline对Redis批量读写(hmset&hgetall)
- java小数位的例子
- @ElementCollection Java Persistence(Hibernate)导致加载重
- java – 用Spring Boot修复飞行路
- Java中的泛型
- java – Slack请求验证:无法使用signed secret计算匹配请
- (翻译) Poor misunderstood 'var'
- 如何使用Jackson序列化/反序列化java.util.stream.Stream?
- Java 8并行流对序列使用相同的线程
- java – JPA中的Null Version字段?