如何在Java中分配超过-Xmx限制的内存-阿里云开发者社区

开发者社区> -开发者助手-> 正文

如何在Java中分配超过-Xmx限制的内存

简介: 本文主要介绍Java中几种分配内存的方法。我们会看到如何使用sun.misc.Unsafe来统一操作任意类型的内存。以前用C语言开发的同学通常都希望能在Java中通过较底层的接口来操作内存,他们一定会对本文中要讲的内容感兴趣。
+关注继续查看

本文主要介绍Java中几种分配内存的方法。我们会看到如何使用sun.misc.Unsafe来统一操作任意类型的内存。以前用C语言开发的同学通常都希望能在Java中通过较底层的接口来操作内存,他们一定会对本文中要讲的内容感兴趣。

如果你对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来访问数组的数据,你需要两个东西:

  • 数组对象里数据的偏移量
  • 拷贝的元素在数组数据里的偏移量

Arrays和Java别的对象一样,都有一个对象头,它是存储在实际的数据前面的。这个头的长度可以通过unsafe.arrayBaseOffset(T[].class)方法来获取到,这里T是数组元素的类型。数组元素的大小可以通过unsafe.arrayIndexScale(T[].class) 方法获取到。这也就是说要访问类型为T的第N个元素的话,你的偏移量offset应该是arrayOffset+N*arrayScale


你可以看下这篇文章里,UnsafeMemory类使用Unsafe访问内存的那个例子。

我们来写个简单的例子吧。我们分配一个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 )

{

    unsafe.putByte( ar, longArrayOffset + 8L * index + i, (byte) 0);

    System.out.println( "After change: i = " + i + ", val = "  +  Long.toHexString( ar[ index ] ));

}

想运行上面 这个例子的话,得在你的测试类里加上下面的静态代码块:

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.allcateMemory(long)来进行内存分配。

第一个方法只是从算法的角度来看比较有意思一点,所以我们还是来看下第二个方法。

sun.misc.Unsafe提供了一组方法来进行内存的分配,重新分配,以及释放。它们和C的malloc/free方法很像:

  • long Unsafe.allocateMemory(long size)——分配一块内存空间。这块内存可能会包含垃圾数据(没有自动清零)。如果分配失败的话会抛一个java.lang.OutOfMemoryError的异常。它会返回一个非零的内存地址(看下面的描述)。
  • Unsafe.reallocateMemory(long address, long size)——重新分配一块内存,把数据从旧的内存缓冲区(address指向的地方)中拷贝到的新分配的内存块中。如果地址等于0,这个方法和allocateMemory的效果是一样的。它返回的是新的内存缓冲区的地址。
  • Unsafe.freeMemory(long address)——释放一个由前面那两方法生成的内存缓冲区。如果address为0什么也不干 。


这些方法分配的内存应该在一个被称为单寄存器地址的模式下使用: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里分配的何种内存,你都可以随意读写任意类型的数据。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10018 0
Java包及访问控制权限--(private/default/protected/public)及命名规范
<h1>1、访问控制权限</h1> <div> <img src="http://img.blog.csdn.net/20131010145930187" alt=""><br> </div> <div> <img src="http://img.blog.csdn.net/20131010150004125" alt=""><br> </div> <div> <pre name=
1338 0
Java包及访问控制权限--包的定义和导入---package
<h1>1、包的定义</h1> <div> <img src="http://img.blog.csdn.net/20131010083059390" alt=""><br> </div> <div> <img src="http://img.blog.csdn.net/20131010083243359" alt=""><br> </div> <div>其中:  <strong>.
1874 0
Java中的访问控制权限
简介 Java中为什么要设计访问权限控制机制呢?主要作用有两点: (1)为了使用户不要触碰那些他们不该触碰的部分,这些部分对于类内部的操作时必要的,但是它并不属于客户端程序员所需接口的一部分。 (2)为了让类库设计者可用更改类的内部工作方式,而不必担心会对用户造成重大影响。
669 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
10880 0
方圆之内-继承相关限制 | 带你学《Java面向对象编程》之三十八
本节结合案例着重介绍了继承的两个限制,分别为多重继承在Java中不可行,继承类无法直接访问父类私有属性。
674 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13819 0
Kubernetes之路 1 - Java应用资源限制的迷思
随着容器技术的成熟,越来越多的企业客户在企业中选择Docker和Kubernetes作为应用平台的基础。然而在实践过程中,还会遇到很多具体问题。本文分析并解决了Java应用在容器使用过程中关于Heap大小设置的一个常见问题。
15206 0
JAVA程序的内存分配
栈区 由编译器自动分配和释放,存放临时变量和函数参数值等 堆区 由程序员分配和释放,如果程序员不手动释放,则由GC或操作系统释放 代码区 存放函数体二进制代码 全局区 存放全局变量 静态区 ...
512 0
2450
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载