赣州新闻网

首页 / 科技

天美汽车发布BE11官方图,NEDC续航里程约为500km

2020-04-03 · 赣州新闻网 · 科技

概述

Java 作为一个面向对象语言,给我们带来了多态,继承,封装等特性,使得我们可以利用这些特性很轻松的就能构建出易于扩展,易于维护的代码。作为一个Javaer,天天搞“对象”,那你写的对象究竟占用了多少内存呢?我们来看看你的“Q ? @ a W V ! . Z对象”是如何“败家”的。

本文环境:jdk1.8_64

Java 对象头内存模型

我们先来看看,一个Java 对象的内存模型是怎么样的?由于我们的虚拟机是分 x l为32位和64位,那肯定它们4 l K的模型也是有区别的,下面我列出列32位虚拟机和64位虚拟机下的Java对象头内存模型。

你写的Java对象究竟占多少内存?

你写的Java对象究竟占多少内存?

你写的Java对象究竟占多少内存?

因为笔者的本地环境是jdk1.8,64位虚拟机,这里我以64位虚拟机(开启指针压缩)来分析,因为h * t =默认情况下,jdk1.8 在64位虚拟机默认开启指针压缩。

Java 对象头主要包括两部分,第一: l ] W % @ _ *部分就是 Mark Word,这也是 Java 锁实现原理中重要的一环,另外一部分是 Klass Word。

Klass Word 这里其M B N V F _ 8 D c实是虚拟k } *机设计的一个oop-klass model模型,这里的OOP是指Ordinary Object Pointer(普通对象指针),看起来g 0 z r u c k像个指针实际上是藏在指针里的对象。而 klass 则包含 元数据和方法信息,用来描述 Java 类。它在64位虚拟机开启压缩指针的环境下占用 32bits 空间。

Mark Word 是我们分析的重点,这里也会设计到锁的s 7 = 1 R S相关知识。Mark Word 在64位虚拟机环境下占用 64bits 空间。整个Mark Word的分配有几种情况:

  1. 未锁定(Normal): 哈希码(identity_hashcx c l H . * ~ e tode)占用31bits,分代年龄(age)占用4 bits,偏向模式(biased_lock)占用1 bits,锁标记(lock)占用2 bits,剩余26bits 未使用(也就是全为0)
  2. 可偏向(Biased): 线程id 占54bits,epoch 占2 bits,分代年龄(age)占用4 bits,偏向模式(biased_lock)占用1 bits,锁标记(lock)占用2 bits,剩余 1bit 未使用。
  3. 轻量锁定(Lx X c Y 1ightweight Locked): 锁指针占用62bits,锁标记(lock)占用2 bits。
  4. 重量级锁定(Heavyweight Locked):锁指针占用62bits,锁标记(lock)占用2 bits。
  5. GC 标记:标记位占2bits,其余为空(也就是填充0)

以上就是我们对Java对象头内存模型的解析,只要是Java对象,那么就肯定会包括对象头,也就是说这部分内存占用是避免不了的。所以,在笔者64位虚拟机,Jdk1.8(开启了指针压缩)的环境下,任何一个对象,啥也不做,只^ Q k要声明一个类,那么它的内存占用就至少是9` A ^ W u6bits,也就是至少12字节。

验证模型

我们来写点代码来验证一下上述的内存模型,这里推荐openjdk的jol工具,它可以帮助你查看对象内存的占用情况。

首先添e s M U加maven依赖

        <dependency>
<groupId>org.openjdk.@ } # ) 1 g V %jol</groupId>
<arti2 i }factId>jol-core</artifactId>
<version>0.10</version>
</dependency>, K # ` ` A `;

我们先来看看,如果只是新建一个普通的类,什么属性也不添加,占用的空间是l ? J H } 8 B |多少?

/**
* @description:
* @author: luozhoU t t F 0 Y @ 9 ou- u , Q
* @ - 7create: 2020-02-26% i 5 c ; 10:00
**/
public class$ B 0 X x x 9 G 5 NullObject {

}

按照我们之前的Java对象内存模型分析,一个空对象,那就是只有一个对象头部,C ( T 3 7 ] v v在指针压缩的条件下会占用 96 bit,也就是12byte。

运行工具查看空间占用

 public static void main(String args) {
System.out.println(Clm K K c k yassLayout.parseInstance(new NullObject()).toPrintable());
}

上面L 5 ! r这行代码会解析你& 5 @ B v Z L新建一个Nuc T xllObject对象,占8 1 6用了多少内存。我们执行看看结果如何:

你写的Java对象究竟占多少内存?

这里我们发现结果显示:Instm = ) K w oance size:16 b= j c G S 8ytes,结果就是16字节,我们之前预测的12字节不一样,为什么会这样呢?我们看到上图中有3行 object header,每个占用4字节,所以头部就是12字节2 B h 1 M Y,这里和我们的计算是一致的,最后一行是虚拟机填充的4字节,那为什么虚拟机要填充4个字节呢?

内] b S Q ) j存对齐

想要知道为什么虚拟机要填充4个字节,我们需要了解什么是内存对齐?

我们程序员l ] B看内存是这样的:

你写的Java对象究竟占多少内存?

上图- 7 - S I z 3 v表示一个坑一个萝卜的内存读取方式。但j P U y x n ] h I - d l i两个原{ W 5因:

  • 平台(移植性)原因:不是所有的硬件平台都能够访问任意地址上的任意数据。例如:特定的硬件平 + ! % / I台只允许在特定地址获取特定类型的数据,否则会导致异常情况。
  • 性能原因:若访问未对齐的内存,将会导致 CPU 进行两次内存访问,并且要花费额外的时钟周期来处理对齐及运算。而本身就对齐的内存仅需要一次访问就可以完成读取动作。

我用图例来说明 CPU 访问非内存对齐的过程:

你写的Java对象究竟占多少内存?

在上图中,假设CPU 是一次读取4字节,在这个连续的8X K L 7 f字节的内存空间中,如果我的数据没有对齐,存储的内存块在地址1,2,3,4中,那CPU的读取就会需要进行两次读取,另外还有额外的计算操作:

  1. CPU 首次读取? G - u ,未对齐地U M p x G址的第一个内存S Q $ = J p -块,读取 0-3 字节。并移除不需要的O G R u & 2字节 0。
  2. CPU 再次读取未对齐地址的第二个内存块,读取 4-7 字节。并移除不需要的字节 5、6、7 字节。
  3. 合9 b ; G P (并 1-4 字节的数据。
  4. 合并后放入寄存器。

所以,没有进行内存对齐就会导致CPU进行额外的读取操作,并且需要额U b B外的计算。如果做了内存s s V q B对齐,CPV # } B I Q !U可以直接从地址0开始读w s H取,一次就读取到G a x O I想要的数% ^ 3 . + t据,不需要进行额外读取操作和运算操作,节省了运行时间。我们用了空间换时间,这就是| e } j为什么我B t b -们需要内存G B ^ 1 q R J对齐。

回到JavF M za空对象填充了4个字节的问题,因为原字节头是12字+ @ C F g j D节,64位机器下,内存对# a + k ( x * k齐的话就是128位,也就是16字节,所以我们还需要填充4个字节。

非空对象占用内存计算

我们知道了一个空对象是占用16字节,那么一个非空对象究竟占用多少字节呢?我们还是写一个普通类来验证下:

p) O S [ 4 C Yublic class TestNotNull {O L i / k
private NullObject nullOK n 1 .bject=new NullObject(q p ~ 8 X ?);
private int a;
}

这~ F a ( 5个演示类中引入了别的对象,我们知道int类型是占用4个字节,NullObject对象占用16t ? T ? b m 2 ,字节,对象头占12字节,还有一个很重要的情况 NullObject在当前这个类中是一个引用,所以不会存真正的对象,而只存引用地址,引用地址占4字节,所以总共就是12+4+4=20字节,内存对齐后m H 0 ;就是24字节。我们来验证0 G s下是不是这个结果:u o ; !

public static void main(String av . i D P U $ ; urgs) {
//打印T I D g F I U实例的内存布局
System.out.pr& ) S d f ]intln(ClassLay) u _ v } k aout.parseInstance(new TestNv , o ~ qotNull()).toPrU H 8 = O S C Z yintable());
//打印对象的所有相关内存占用
System.out.o 8 4 =println(GraphLayout.parseInstance(new TestNotNull()).toPrinta 5 H Table());
//打印对象的所有内s ` ^ f存结果并统计
System.out.println(GraphLayout.parseInstance(new TestNotNull()).toFootprint());
}

结果如下:

你写的Java对象究竟占多少内存?

我们可以看到TestNotNe = wull的类占f g J ]用空间是24字节,其中头部占用12字节,变量a是int类型,占用4字节,变量nullObject是引用,占用了4字节,最后填充了4个字节,总共是24个字节,与我们之前的预测一致。但是,因为我们实例化了NullObject,这个对象一会存在于内存中,所以我们还需要加h 7 q上这个对象的内存占用16字节,那总共就是24bytes+16bytes=40bP | { Q 9 G oytes。我们图中最后的统计打印结果也是40字节,所以我们, n } s i 7的分析正确。

这也是如何分析一个对象真正的占用多少内存的思路,根据这个思路加上openJDK的jol工具就可以基本的掌握自己写的“对象”究竟败家了你多少内存。

总结

本文我主要讲述了如何分析一个Java对象究竟占用多少内存空间,主要总结点如下:

  1. Java对象头部内存模型在32位虚拟机和64位虚拟机是不一样的,64位虚拟机又分为开启指针压缩和不开启指针压缩两种对象头模型,所以总共有3种对象头模型。
  2. 内存对齐主要是因为平台的原因和性能的原因,本文主要解析的是性能方面的原因。
  3. 空对象的内存占用D v 6 d计算注意要计g E ?算内存对/ 3 9 9齐a g ^ [ , j,非空对象的内存计算注意加上引用内存占用和原实例对象的6 @ B Q 0 P x B h空间占用。

汽车新建维护推荐动作续航续航里程发布大余