• JVM内存不要超过32G


    不要超过32G


    事实上jvm在内存小于32G的时候会采用一个内存对象指针压缩技术。
     
    在java中,所有的对象都分配在堆上,然后有一个指针引用它。指向这些对象的指针大小通常是CPU的字长的大小,不是32bit就是64bit,这取决于你的处理器,指针指向了你的值的精确位置。
     
    对于32位系统,你的内存最大可使用4G。对于64系统可以使用更大的内存。但是64位的指针意味着更大的浪费,因为你的指针本身大了。浪费内存不算,更糟糕的是,更大的指针在主内存和缓存器(例如LLC, L1等)之间移动数据的时候,会占用更多的带宽。
     
    Java 使用一个叫内存指针压缩的技术来解决这个问题。它的指针不再表示对象在内存中的精确位置,而是表示偏移量。这意味着32位的指针可以引用40亿个对象,而不是40亿个字节。最终,也就是说堆内存长到32G的物理内存,也可以用32bit的指针表示。
     
    一旦你越过那个神奇的30-32G的边界,指针就会切回普通对象的指针,每个对象的指针都变长了,就会使用更多的CPU内存带宽,也就是说你实际上失去了更多的内存。事实上当内存到达40-50GB的时候,有效内存才相当于使用内存对象指针压缩技术时候的32G内存。
     
    这段描述的意思就是说:即便你有足够的内存,也尽量不要超过32G,因为它浪费了内存,降低了CPU的性能,还要让GC应对大内存。

    JVM内存不要超过32G_zsj777的专栏-CSDN博客_jvm内存超过32g

    1、将Java Heap Size设置的大于32G会对性能有什么影响?

    开门见山的说,结果有几点(这几点其实也是内部关联):

    • 触发JVM的临界值,优化策略Compressed OOPS失效(之前Heap Size在[4G~32G]区间内采用此优化)

    • 由于优化策略失效,同时堆内存>32G,所以JVM被迫使用8字节(64位)来对Java对象寻址(之前4字节(32位)就够了)

    • 通常64位JVM消耗的内存会比32位的大1.5倍,这是因为对象指针在64位架构下,长度会翻倍(事实上当内存到达40-50GB的时候,有效内存才相当于使用Compressed OOPS技术时候的32G内存)

    • 更大的指针在主内存和缓存器(例如LLC, L1等)之间移动数据的时候,会占用更多的带宽

    • 让JVM的GC面临更大压力的指针对象(在实际应用中构建大于12-16G的堆时,若无很好的性能调优与测评,你很容易就会引起一个耗时数分钟的完全GC)

    1.1 JVM的OOPS

    OOP = “ordinary object pointer” 普通对象指针

    启用CompressOops后,会压缩的对象:

    • 每个class的属性指针(静态成员变量)

    • 每个对象的属性指针

    • 普通对象数组的每个元素指针

    1.2 JVM的优化策略Compressed OOPS

    从JDK 1.6 update14开始,64 bit JVM正式支持了 -XX:+UseCompressedOops ,这个可以压缩指针,起到节约内存占用的新参数。

    Compressed OOPS,即大雾的对象压缩技术,压缩引用到32位,以降低堆的占用空间。其伪代码原理就不贴了,

    在堆大小在[4G~32G]的时候,这项技术会被触发,在JVM执行时加入编/解码指令,即

    JVM在将对象存入堆时编码,在堆中读取对象时解码

    内存地址确定公式类似于

    1
    <narrow-oop-base(64bits)> +(<narrow-oop(32bits)><< 3) +<field-offset>

    Zero Based Compressed OOPS(零基压缩优化)则进一步将基地址置为0(并不一定是内存空间地址为0,只是JVM相对的逻辑地址为0,如可用CPU的寄存器相对寻址) 这样转换公式变为:

    1
    (<narrow-oop << 3) +<field-offset>

    从而进一步提高了压解压效率。

     

    使用Zero Based Compressed OOPS后,它的指针不再表示对象在内存中的精确位置,而是表示偏移量。这意味着32位的指针可以引用40亿个对象,而不是40亿个字节。

    1.3 Zero Based Compressed OOPS的多种策略

    它可以针对不同的堆大小使用多种策略,具体可以 ps + grep查看:

    • 堆小于4G,无需编/解码操作,JVM会使用低虚拟地址空间(low virutal address space,64位下模拟32位)

    • 小于32G而大于4G,使用Zero Based Compressed OOPS

    • 大于32G,不使用Compressed OOPS

    2、结论

    • Compressed OOPS,可以让跑在64位平台下的JVM,不需要因为更宽的寻址,而付出Heap容量损失的代价

    • 它的实现方式是在机器码中植入压缩与解压指令,可能会给JVM增加额外的开销

     为什么JVM开启指针压缩后支持的最大堆内存是32G? - 知乎

    为什么JVM开启指针压缩后支持的最大堆内存是32G?

     

    1.在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力

    2.为了减少64位平台下内存的消耗,启用指针压缩功能

    3.在jvm中,32位地址表示4G个对象的指针,在4G-32G堆内存范围内,可以通过编码、解码方式进行优化,使得jvm可以支持更大的内存配置

    4.堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间

    5.堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内存不要大于32G为好

    先放出结论:

    如果配置最大堆内存超过 32 GB(当 JVM 是 8 字节对齐),那么压缩指针会失效。 但是,这个 32 GB 是和字节对齐大小相关的,也就是-XX:ObjectAlignmentInBytes配置的大小(默认为8字节,也就是 Java 默认是 8 字节对齐)。-XX:ObjectAlignmentInBytes可以设置为 8 的整数倍,最大 128。也就是如果配置-XX:ObjectAlignmentInBytes为 24,那么配置最大堆内存超过 96 GB 压缩指针才会失效。

    压缩指针这个属性默认是打开的,可以通过-XX:-UseCompressedOops关闭。

    首先说一下为何需要压缩指针呢?32 位的存储,可以描述多大的内存呢?假设每一个1代表1字节,那么可以描述 0~2^32-1 这 2^32 字节也就是 4 GB 的内存。

     

     

    但是呢,Java 默认是 8 字节对齐的内存,也就是一个对象占用的空间,必须是 8 字节的整数倍,不足的话会填充到 8 字节的整数倍。也就是其实描述内存的时候,不用从 0 开始描述到 8(就是根本不需要定位到之间的1,2,3,4,5,6,7)因为对象起止肯定都是 8 的整数倍。所以,2^32 字节如果一个1代表8字节的话,那么最多可以描述 2^32 * 8 字节也就是 32 GB 的内存。

     

     

    这就是压缩指针的原理。如果配置最大堆内存超过 32 GB(当 JVM 是 8 字节对齐),那么压缩指针会失效。 但是,这个 32 GB 是和字节对齐大小相关的,也就是-XX:ObjectAlignmentInBytes配置的大小(默认为8字节,也就是 Java 默认是 8 字节对齐)。-XX:ObjectAlignmentInBytes可以设置为 8 的整数倍,最大 128。也就是如果配置-XX:ObjectAlignmentInBytes为 24,那么配置最大堆内存超过 96 GB 压缩指针才会失效。

     

    编写程序测试下:

    A a = new A();
    System.out.println("------After Initialization------\n" + ClassLayout.parseInstance(a).toPrintable());

    首先,以启动参数:-XX:ObjectAlignmentInBytes=8 -Xmx16g执行:

    ------After Initialization------
    com.hashjang.jdk.TestObjectAlign$A object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
         12     4        (alignment/padding gap)                  
         16     8   long A.d                                       0
    Instance size: 24 bytes
    Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

    可以看到类型字大小为 4 字节48 72 06 00 (01001000 01110010 00000110 00000000) (422472),压缩指针生效。

    首先,以启动参数:-XX:ObjectAlignmentInBytes=8 -Xmx32g执行:

    ------After Initialization------
    com.hashjang.jdk.TestObjectAlign$A object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           a0 5b c6 00 (10100000 01011011 11000110 00000000) (12999584)
         12     4        (object header)                           b4 02 00 00 (10110100 00000010 00000000 00000000) (692)
         16     8   long A.d                                       0
    Instance size: 24 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

    可以看到类型字大小为 8 字节,压缩指针失效:

    a0 5b c6 00 (10100000 01011011 11000110 00000000) (12999584)
    b4 02 00 00 (10110100 00000010 00000000 00000000) (692)

    修改对齐大小为 16 字节,也就是以-XX:ObjectAlignmentInBytes=16 -Xmx32g执行:

    ------After Initialization------
    com.hashjang.jdk.TestObjectAlign$A object internals:
     OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
          0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
          4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
          8     4        (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
         12     4        (alignment/padding gap)                  
         16     8   long A.d                                       0
         24     8        (loss due to the next object alignment)
    Instance size: 32 bytes
    Space losses: 4 bytes internal + 8 bytes external = 12 bytes total

    可以看到类型字大小为 4 字节48 72 06 00 (01001000 01110010 00000110 00000000) (422472),压缩指针生效。

    开启-XX:+UseCompressedOops后对象计算有三种模式

    (1) 如果堆的高位地址小于32G,说明不需要基址(base)就能定位堆中任意对象,这种模式也叫做Zero-based Compressed Oops Mode

    (2) 如果堆高位大于等于32G,说明需要基地址,这时如果堆大小小于4G,说明基址+偏移能定位堆中任意对象

    (3) 如果堆处于4G到32G的范围,这时只能通过基址+偏移x缩放(scale)才能定位堆中任意对象

    如果有shift的存在,对象地址还必须8字节对齐8,如果不幸堆大于32G,那么无法使用压缩对象指针。

     

  • 相关阅读:
    [Clr via C#读书笔记]Cp18 定制Attribute
    [Clr via C#读书笔记]Cp16数组
    [Clr via C#读书笔记]Cp17委托
    [Clr via C#读书笔记]Cp15枚举和位标识
    [Clr via C#读书笔记]Cp14字符字符串和文本处理
    [Clr via C#读书笔记]Cp13接口
    [Clr via C#读书笔记]Cp12泛型
    [Clr via C#读书笔记]Cp11事件
    [Clr via C#读书笔记]Cp10属性
    【程序员面试金典】面试题 02.06. 回文链表
  • 原文地址:https://www.cnblogs.com/Chary/p/15891674.html
Copyright © 2020-2023  润新知