首页 > 系统相关 >Java对象内存布局

Java对象内存布局

时间:2023-04-26 22:33:58浏览次数:53  
标签:00 object Java 00000000 布局 bytes 对象 内存 指针

一、对象在堆内存中布局

Object object = new Object()

一般而言JDK8按照默认情况下,new一个对象占多少内存空间

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

二、对象在堆内存中的存储布局

下面分别是java对象数组(数组对象会多一个length),原理其实类似

2.1 对象头

  • 对象头分为对象标记(markOpp)类元信息(klassOop)
  • 类元信息存储的是指向该对象类元数据(klass)的首地址。
public class Demo01 {
    public static void main(String[] args) {
        Object o = new Object();// 一个对象,内存占多少,记录在哪里?

        System.out.println(o.hashCode());//356573597,这个hashCode又是记录在哪里的

        synchronized (o) {//加锁信息又是记录在哪里的
        }
        System.gc();//手动垃圾收集中,15次可以从新生代到养老区,那这个次数又是记录在哪里的
    }
}
  • 刚刚几个问题都保存在对象标记

2.1.1 对象标记Mark Word

在64位系统中,MarkWord占了8个字节,类型指针占了8个字节,一共是16个字节

默认存储对象的HashCode、分代年龄和锁标志位等信息。这些信息都是与对象自身定这无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord上存储的数据会随着锁标志位的变化而变化。


32位与64位对比

  • 32位

看一下即可,不用学了,以64位为准

  • 64位重要

C中的源码

  • oop.hpp

  • markOop.hpp

字段 描述
hash 保存对象的哈希码
age 保存对象的分代年龄
biased_lock 偏向锁标识位
lock 锁状态标识位
JavaThread* 保存持有偏向锁的线程ID
epoch 保存偏向时间戳

官网

  • Hotspot术语表官网

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

  • 底层源码理论证明

http://hg.openjdk.java.netjidlk8u/jdk8u/hotspot/file/89fb452b3688/src/share/vm/oops/oop.hpp

_mark字段是mark word,metadata是类指针klass pointer,对象头(object header)即是由这两个字段组成,这些术语可以参考Hotspot术语表,

  • markWord(64位)分布图

对象布局、GC回收和后面的锁升级就是对象标记MarkWord里面标志位的变化

2.1.2 类元信息(又叫类型指针)Class Pointer

所谓的类元信息(类型指针)其实就可以说是模板

  • 对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的示例。

2.2 实例数据

实例数据:存放类的属性(Field)信息,包括父类的属性信息

2.3 对齐填充

用来保证8字节的倍数

对齐填充:虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐这部分内存按8字节补充对齐。

有个案例,对象头16+实例数据5+对齐填充3=24字节

三、代码演示

3.1 JOL证明

JOL工具(Java Object Layout工具)-可以帮助分析对象在Java虚拟机中的大小和布局

我们可以直接用依赖来实现这个功能

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
//简单测试
public static void main(String[] args) {
    //Vm的细节详细情况
    System.out.println(VM.current().details());
    //# WARNING: Unable to attach Serviceability Agent.
    // You can try again with escalated privileges. Two options: 
    // a) use -Djol.tryWithSudo=true to try with sudo; 
    // b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
    //# Running 64-bit HotSpot VM.
    //# Using compressed oop with 3-bit shift.
    //# Using compressed klass with 3-bit shift.
    //# WARNING | Compressed references base/shifts are guessed by the experiment!
    //# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
    //# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
    //# Objects are 8 bytes aligned.
    //# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
    //# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
        
    //所有的对象分配的字节都是8的整数倍
    System.out.println(VM.current().objectAlignment());
    //8
}

3.2 代码

3.2.1 演示一_用自带的类

//第一个演示,16bytes演示
public class Demo01 {
    public static void main(String[] args) {
        Object o = new Object();//----------新建一个Object对象就是  16bytes
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        //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 f8 (11100101 00000001 00000000 11111000) (-134217243)
        //     12     4        (loss due to the next object alignment)
        //Instance size: 16 bytes
        //Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    }
}

3.2.2 演示二_用自己的类

//只有对象头,没有实例数据,依然是16byte
public class Demo01 {
    public static void main(String[] args) {
        Customer c1 = new Customer();
        System.out.println(ClassLayout.parseInstance(c1).toPrintable());
        //com.zhang.java.Customer 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)        43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
        //     12     4        (loss due to the next object alignment)
        //Instance size: 16 bytes
        //Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    }
}

class Customer {

}
//有了对象头,且有实例数据(int+boolean),它进行了对齐填充,到了24byte
public class Demo01 {
    public static void main(String[] args) {
        Customer c1 = new Customer();
        System.out.println(ClassLayout.parseInstance(c1).toPrintable());
        //com.zhang.java.Customer 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)        43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
        //     12     4       int Customer.id            0
        //     16     1   boolean Customer.flag          false
        //     17     7           (loss due to the next object alignment)
        //Instance size: 24 bytes
        //Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
    }
}

class Customer {
    int id;
    boolean flag = false;
}

GC年龄采用4位bit存储,最大位15,例如MaxTenuringThreshold参数默认值就是15,对象分代年龄最大就是15

3.3 证明

  • 我们假如想直接把分代最大年龄修改为16会直接报错。
-XX:MaxTenurningThreshold=16

尾巴参数说明(压缩指针相关)

压缩指针相关的命令(压缩指针是否开启对我们new一个对象是不是16字节的影响)

查看当前JVM运行参数的指令

java -XX:+PrintCommandLineFlags -version

压缩指针默认是开启的(这也就解释了为什么前面的类型指针是4个字节,节约了内存空间)

3.4 关闭压缩指针

+是开启,-就是关闭,所以指令是

-XX:-UseCompressedClassPointers

也要注意,不管是否开启压缩指针,创建一个对象就是16字节的。(开启压缩指针后缺失的会由对齐填充补充)

  • 换成其他对象试试

同样的道理

标签:00,object,Java,00000000,布局,bytes,对象,内存,指针
From: https://www.cnblogs.com/ciel717/p/17268620.html

相关文章

  • java 并发编程-基础篇
    java创建线程的三种方法直接使用Thread//创建线程对象Threadt=newThread(){publicvoidrun(){//要执行的任务}};//启动线程t.start();Runable配合Thread把线程和任务分开。Runnablerunnable=newRunnable(){publicvoidrun(......
  • Redis内存淘汰策略
    Redis内存淘汰策略是指Redis用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据 全局的键空间选择性移除noeviction:当内存不足以容纳新写入数据时,新写入操作会报错allkeys-lru:当内存不足以容纳新写入数据时,在键空间中移除最近最少使用的keyallkeys-random:当内......
  • Java Lambda Stream
    ::方法使用条件:lambada表达式的主体仅包含一个表达式,且lambada表达式只调用一个已经存在的方法;被引用的方法的参数列表与lambada表达式的输入输出一致以下是Java8中方法引用的一些语法:静态方法引用(staticmethod)语法:classname::methodname例如:Person::getAge对象的实例方......
  • java线程之Semaphore
    Semaphore是信号量,用于线程间同步。例子,公交车上大概有24个座位,但是车上总共有40个人,那么,只要有一个人下车,另一个人就能得到一个座位,假设到终点站之前,每个人都能坐下。代码如下:packagecom.concurrent;importjava.util.Random;importjava.util.concurrent.CountDownLatch;imp......
  • java线程之FutureTask
    FutureTask是线程的异步计算。如果有多个线程,每个线程都要花很多时间计算,而且所花时间不同,最后要统计,就要用到此类。此类有个done方法,等call完后,执行此方法。代码:packagecom.concurrent;importjava.util.ArrayList;importjava.util.List;importjava.util.Random;importja......
  • java线程之wait、notifyAll
    wait、notifyAll是线程之间用来通信的,设计模式里的观察者模式。例子,上课前,同学在玩,一个同学观察老师是不是来了,如果来了,叫其他同学坐好。packagecom.concurrent;importjava.util.HashSet;importjava.util.Set;importjava.util.concurrent.CountDownLatch;importjava.util......
  • java之用volatile和不用volatile的区别
    在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。 要解决这个问题,只需要像在本程序中的这样,把该变量......
  • java流程控制
    scanner创建一个新的扫描器对象,用于接受键盘数据Scannerscanner=newScanner(System.in);//使用next方式接收Stringsrt=scanner.next();//使用nextLine方式接收Stringsrt=scanner.nextLine();next():一定要读取到有效字符后才可以结束输入对输入有效字符之前......
  • 深入java虚拟机 - 垃圾收集 - 引用计数收集器
         引用计数是垃圾收集的早期策略。在这种方法中,堆中每一个对象都有一个引用计数。一个对象被创建了,并且指向该对象的引用被分配给一个变量,这个对象的引用计数被置为1。当任何其他变量被赋值为对这个对象的引用时,计数加1。当一个对象的引用超过了......
  • java 多线程 synchronized
    程序1:packagetestsynchronized;publicclassThread1implementsRunnable{ @Override publicvoidrun(){ synchronized(this){ for(inti=0;i<10;i++){ System.out.println(Thread.currentThread().getName() +"synchronizedloo......