`
deepinmind
  • 浏览: 451396 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41617
社区版块
存档分类
最新评论

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

 
阅读更多

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

如果你对Java内存优化比较感兴趣,可以看下这篇文章,以及它的姊妹篇:。http://java-performance.info/memory-consumption-of-java-data-types-2/

数组分配的上限

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里分配的何种内存,你都可以随意读写任意类型的数据。

原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接

想及时了解博客更新,可以关注我的微博Java译站



2
0
分享到:
评论

相关推荐

    java jvm 参数 -Xms -Xmx -Xmn -Xss -

    在JVM中,内存管理是至关重要的,而`-Xms`, `-Xmx`, `-Xmn`, `-Xss`等参数则直接影响着Java应用程序的性能和稳定性。这些参数是用来调整JVM堆内存和线程栈大小的。 1. `-Xms`: 这个参数用于设置JVM启动时初始的堆...

    JVM调优总结 -Xms -Xmx -Xmn -Xss

    在 32 位系统下,一般限制在 1.5G~2G,而 64 位操作系统对内存无限制。 -Xmx 设置 JVM 的最大可用内存,-Xms 设置 JVM 的初始内存大小。二者的值可以相同,以避免每次垃圾回收完成后 JVM 重新分配内存。 -Xmn ...

    JVM调优总结 Xms -Xmx -Xmn -Xss

    当-Xms与-Xmx相同时,JVM在启动时会分配一个固定大小的堆内存,从而避免了动态调整带来的性能开销。 - **示例**:`java -Xms3550m ...` #### 2. -Xmx(Maximum Heap Size) - **定义**:设置JVM可以使用的最大堆...

    认识 java JVM虚拟机选项 Xms Xmx PermSize MaxPermSize 区别

    在实际应用中,服务器通常设置 Xms 和 Xmx 相等,以避免在每次 GC 后调整堆的大小。如果 Xmx 不指定或者指定偏小,应用可能会导致 java.lang.OutOfMemory 错误。如果 MaxPermSize 设置过小,可能会导致 java.lang....

    java IBM websphere 内存溢出 javacore deapdump CPU内存分析工具

    Java IBM WebSphere应用服务器在运行过程中可能会遇到各种性能问题,其中最常见的挑战之一是内存溢出。内存溢出是指应用程序消耗的内存超过了系统所能提供的限制,导致程序崩溃或性能急剧下降。在这种情况下,开发者...

    JVM参数-Xms-Xmx-Xmn-Xss-调优总结.docx

    根据应用的线程需求调整此值,减少该值可以在物理内存限制下创建更多线程,但操作系统对进程内的线程数量仍有限制。 4. 其他高级参数: `-XX:NewRatio` 设置年轻代与年老代的比例,如`-XX:NewRatio=4`意味着年轻代...

    java虚拟机jvm及Tomcat中的jvm有关内存的设置与调优

    2. **-Xmx**:设定JVM最大堆内存大小,不应超过物理内存的限制,以防内存溢出。 3. **-Xmn**:设置年轻代内存大小,通常不需要手动设置,JVM会根据实际情况自动调整。 4. **-Xss**:设定每个线程的栈大小,对于...

    JVM内存参数详解以及配置调优

    在这个资源中,我们将详细讨论 JVM 内存参数的配置和调优,包括 JVM 的结构、内存管理、垃圾回收、堆和非堆内存、内存分配和限制等方面。 JVM 结构 JVM 的结构主要由六个部分组成:JVM API、JVM 内部组件、平台...

    JVM初始分配的内存.doc JVM初始分配的内存.doc

    原有值可能为`-Dcatalina.home="C:\ApacheGroup\Tomcat 5.0" -Djava.endorsed.dirs="C:\ApacheGroup\Tomcat 5.0\common\endorsed" -Xrs`,需要在此基础上加入`-Xms300m -Xmx350m`以设置初始堆内存为300MB,最大堆...

    Eclipse内存分配

    在Java 8及更高版本中,非堆内存被替换为元空间(Metaspace),其大小不再受固定大小的限制,而是由可用的本机内存决定。 #### 三、内存分配参数详解 根据题目提供的部分内容,我们可以看到以下参数: - `-vmargs...

    编译时出现java.lang.OutOfMemoryError Java heap space异常

    这类异常通常表明Java虚拟机(JVM)在运行过程中遇到了内存不足的问题,特别是当堆内存无法满足程序的需求时。 #### 二、原因分析 1. **项目规模过大**:如果项目中的类文件数量过多或者单个对象占用内存较大时,...

    JAVA内存溢出详解.doc

    Java内存溢出(Out Of Memory,OOM)是Java应用程序运行时常见的问题,它通常发生在程序对内存需求超过了Java虚拟机(JVM)所能提供的可用内存时。本文将深入探讨Java内存溢出的原因、表现以及如何解决。 1. **Java...

    java抛java heap space

    这种错误通常发生在应用程序对内存的需求超过了 JVM 能够提供的最大堆空间限制时。 #### 描述:解决 Java 抛出 Java Heap Space 错误的方法 当 Java 程序运行过程中遇到内存溢出问题时,需要采取有效措施来诊断并...

    could not create the java virtual machine 解决办法

    在开发过程中,我们经常会遇到 “could not create the java virtual machine” 这样的错误提示。这个问题通常出现在启动基于Java的应用程序时,比如MyEclipse等集成开发环境(IDE)。下面将详细介绍这一问题的原因...

    Inside the JVM

    - 在32位系统中,通常最大不超过2GB。 - 64位系统则可以支持更大的内存。 - **配置**: - `-Xmx`设置Java对象堆的最大大小。 - `-Xms`设置Java对象堆的初始大小。 - 示例:`-Xmx1800m -Xms1800m`表示最大和初始...

    kettle内存溢出(Java heap space)以及解决方法.docx

    1. **大数据量处理**:当Kettle处理大量数据时,如在表输入、表输出、聚合、Join、过滤等步骤中,如果一次性加载太多数据到内存,可能会超出JVM的堆内存限制。 2. **内存管理不当**:Kettle的默认配置可能不适合特定...

    行业分类-设备装置-BD-java平台上的最小内存自适应机制及使用方法.zip

    "BD-java平台上的最小内存自适应机制及使用方法"这一主题聚焦于如何在Java环境中实现内存的高效利用,特别是通过最小内存自适应机制来优化性能。 Java的内存模型通常分为三个主要区域:堆内存(Heap)、栈内存...

    JAVA-WEB系统性能调优.doc

    2.1.4、JVM内存限制(最大值):JVM内存限制(最大值)是指JVM可以分配的最大内存值。该值可以通过-Xmx参数来控制。 2.2、JVM参数详解:JVM参数是指JVM的配置参数,包括堆内存分配、非堆内存分配、垃圾回收参数等。...

    Tomcat内存溢出的三种情况及解决办法分析

    内存中的一部分必须用于系统dll的加载,那么真正剩下的也许只有400M,现在关键的地方出现了:当你使用Java创建一个线程,在JVM的内存里也会创建一个Thread对象,但是同时也会在操作系统里创建一个真正的物理线程...

Global site tag (gtag.js) - Google Analytics