Java多线程高并发(二) java内存布局

一  java对象创建

       对象创建底层原理图:

本文主要是针对内存布局的概述,如果想要对详细的java对象创建过程感兴趣的话:可以参考这篇博客https://blog.csdn.net/justloveyou_/article/details/72466416

二 java内存存储

1、对象在内存中存储的布局分为三块

  • 对象头

    • 存储对象自身的运行时数据:Mark Word(在32bit和64bit虚拟机上长度分别为32bit和64bit),包含如下信息:
      • 对象hashCode
      • 对象GC分代年龄
      • 锁状态标志(轻量级锁、重量级锁)
      • 线程持有的锁(轻量级锁、重量级锁)
      • 偏向锁相关:偏向锁、自旋锁、轻量级锁以及其他的一些锁优化策略是JDK1.6加入的,这些优化使得Synchronized的性能与ReentrantLock的性能持平,在Synchronized可以满足要求的情况下,优先使用Synchronized,除非是使用一些ReentrantLock独有的功能,例如指定时间等待等。(在32位系统占4字节,在64位系统中占8字节;)
    • Class Pointer:对象指向类元数据的指针(32bit-->32bit,64bit-->64bit(未开启压缩指针),32bit(开启压缩指针))                                也就是说在未开启压缩指针的情况下,在64位系统中占8字节;开启压缩指针那么就是4个字节
      • JVM通过这个指针来确定这个对象是哪个类的实例(根据对象确定其Class的指针)

    • Length::如果是数组对象,还有一个保存数组长度的空间,占4个字节;

  • 对象实际数据 

              对象实际数据包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和          char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节(64位系统中是8个字节)。

  • Primitive TypeMemory Required(bytes)
    boolean1
    byte1
    short2
    char2
    int4
    float4
    long8
    double8

    对于reference类型来说,在32位系统上占用4bytes, 在64位系统上占用8bytes。

  • 对齐填充

       Java对象占用空间是8字节对齐的,即所有Java对象占用bytes数必须是8的倍数。例如,一个包含两个属性的对象:int和byte,这个对象需要占用8+4+1=13个字节,这时就需要加上大小为3字节的padding进行8字节对齐,最终占用大小为16个字节。

2.分析内存布局

针对上面的结论,如何进行验证呢?可以通过jol来查看。

导入maven的openjdk.jol包

   <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
    </dependency>

添加-XX:+PrintCommandLineFlags参数   

2.1  创建Object对象

  Object object =new Object();
  System.out.println(ClassLayout.parseInstance(object).toPrintable());

打印结果:

-XX:InitialHeapSize=132141888 -XX:MaxHeapSize=2114270208 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

注明:使用的是jdk1.8 64位(默认开启压缩指针)

-XX:+UseCompressedClassPointers 开启类指针压缩

-XX:+UseCompressedOops :开启普通对象指针压缩

可以看到markword占8个字节,class Pointer占4个字节,然后12不被8整除,补齐4位也就是object占用16个字节

关于这两个指针压缩的关系,我这边做了个测试

1.开启对象指针压缩:-XX:+UseCompressedOops  ( markword 8字节,class Pointer 4字节)

2.关闭对象指针压缩:-XX:-UseCompressedOops  (markword 8字节,class Pointer 8 字节)

3.开启类指针压缩:-XX:+UseCompressedClassPointers (markword 8字节,class Pointer 4字节)

4.关闭类指针压缩:-XX:-UseCompressedClassPointers(markword 8字节,class Pointer 8 字节)

我们发现任何一个单独的类指针或者对象指针的开启跟关闭,都会直接影响结果。得出结论,单独开启一个指针其对应的指针也会一起开启

5.开启类指针,关闭对象指针:-XX:+UseCompressedClassPointers -XX:-UseCompressedOops(markword 8字节,class Pointer 8 字节)

6.关闭类指针,开启对象指针:  -XX:-UseCompressedClassPointers -XX:+UseCompressedOops(markword 8字节,class Pointer 8 字节)

7.开启类指针,关闭对象指针:-XX:+UseCompressedClassPointers -XX:-UseCompressedOops(Java HotSpot(TM) 64-Bit Server VM warning: UseCompressedClassPointers requires UseCompressedOops)

更多的指针压缩可以参考这篇文章:https://blog.csdn.net/qq_33223299/article/details/106354718

2.2  创建对象(基本数据类型)

Person类:

public class People {

    private   int  id;

    private  char sex;

    static  private int age;

}

测试:

        People people=new People();
        System.out.println(ClassLayout.parseInstance(people).toPrintable());

结果:

 

指针压缩情况下:对象头12字节+int 4个字节+char 2个字节+对齐填充 6个字节=24个字节

结论:当对象属性是基本数据类型的时候,那么该对象的大小等于对象头size+基本数据类型属性size+对齐填充size

我们发现加static修饰的属性并没有分配在堆内存中,所以在内存布局中并没有看到

2.3 数组类型

        long[] longs=new long[5];
        System.out.println(ClassLayout.parseInstance(longs).toPrintable());

结果:

 

64位系统中,数组对象的对象头占用24 bytes,启用压缩后占用16字节。比普通对象占用内存多是因为需要额外的空间存储数组的长度。基础数据类型数组占用的空间包括数组对象头以及基础数据类型数据占用的内存空间。由于对象数组中存放的是对象的引用,对象头 16字节+long的大小 8个字节*数量 5=56个字节

但是本人测试了对象数组

2.4 包装类型

包装类(Boolean/Byte/Short/Character/Integer/Long/Double/Float)占用内存的大小等于对象头大小加上底层基础数据类型的大小。

包装类型的Retained Size占用情况如下:

Numberic Wrappers+useCompressedOops-useCompressedOops
Byte, Boolean16 bytes24 bytes
Short, Character16 bytes24 bytes
Integer, Float16 bytes24 bytes
Long, Double24 bytes24 bytes


结论:包装类型实际数据大小与基础数据类型一致

2.5 String类型

在JDK1.7及以上版本中,java.lang.String中包含2个属性,一个用于存放字符串数据的char[], 一个int类型的hashcode, 部分源代码如下:

在关闭指针压缩的时候:

内存大小=对象头大小16字节+int类型大小4字节+数组引用大小8字节+padding4字节=32字节;

 

开启压缩指针:

 

内存大小=对象头大小12字节+int类型大小4字节+数组引用大小4字节+padding4字节=24字节;

参考文章:https://www.jianshu.com/p/91e398d5d17c

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值