Java对象内存结构

转载自 Java对象内存结构

学C/C++出身的我,对Java有一点非常困惑,那就是缺乏计算对象占用内存大小的机制。而在C++中就可以通过sizeof运算符来获得基本类型以及类实例的大小。C和C++中的这个操作符对于指针运算、内存拷贝和IO操作都非常有用。

Java中并没有一个类似的运算符。事实上,Java也不需要这种运算符。Java中基本类型的大小在语言规范中已经定义了,而C/C++中基本类型大小则跟平台相关。Java有自己的通过序列化构建的IO框架。再者,由于Java中没有指针,因此指针运算和内存块拷贝之类的操作也不存在。

但是,Java程序员有时还是希望能知道一个Java对象到底用了多少内存的。不过这个问题的答案并不简单。

首先要区分清楚的是shallow size和deep size。Shallow size是指对象自身占用的内存大小,其引用对象的大小不算在内。而deep size,则是自身所占内存大小和其递归引用的所有对象所占内存大小的总和。大多数情况下,你会希望获得一个对象的deep size,但是为了知道这个值,首先要知道怎么算shallow size,下面我来介绍一下。

有人抱怨JVM规范中没有针对运行时Java对象的内存结构的说明,这也就是说JVM供应商可以按照自己的需要来实现这一点。后果就是,同一个类在不同的JVM上运行的实例对象占用的内存大小会有差别。好在是世界上大部分人(包括我在内)都使用Sun HotSpot虚拟机,这就大大简化了这个问题。我们接下来的讨论也会基于32位的Sun公司的JVM。下面我介绍一些规则来辅助解释JVM如何组织对象在内存中的布局的。

没有实例属性的类的内存布局

在Sun JVM中,(除了数组之外的)对象都有两个机器字(words)的头部。第一个字中包含这个对象的标示哈希码以及其他一些类似锁状态和等标识信息,第二个字中包含一个指向对象的类的引用。另外,任何对象都是8个字节为粒度进行对齐的。这就是对象内存布局的第一个规则:

规则1:任何对象都是8个字节为粒度进行对齐的。

比如,如果调用new Object(),由于Object类并没有其他没有其他可存储的成员,那么仅仅使用堆中的8个字节来保存两个字的头部即可。

继承了Object的类的内存布局

除了上面所说的8个字节的头部,类属性紧随其后。属性通常根据其大小来排列。例如,整型(int)以4个字节为单位对齐,长整型(long)以8个字节为单位对齐。这里是出于性能考虑而这么设计的:通常情况下,如果数据以4字节为单位对齐,那么从内存中读4字节的数据并写入到处理器的4字节寄存器是性价比更高的。

为了节省内存,Sun VM并没有按照属性声明时的顺序来进行内存布局。实际上,属性在内存中按照下面的顺序来组织:

1. 双精度型(doubles)和长整型(longs)

2. 整型(ints)和浮点型(floats)

3. 短整型(shorts)和字符型(chars)

4. 布尔型(booleans)和字节型(bytes)

5. 引用类型(references)

内存使用率会通过这个机制得到优化。例如,如下声明一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
classMyClass {
       bytea;
       intc;
       booleand;
       longe;
       Object f;         
}

如果JVM并没有打乱属性的声明顺序,其对象内存布局将会是下面这个样子:

 

1
2
3
4
5
6
7
8
9
[HEADER: 8bytes]  8
[a:      1byte 9
[padding:3bytes] 12
[c:      4bytes] 16
[d:      1byte ] 17
[padding:7bytes] 24
[e:      8bytes] 32
[f:      4bytes] 36
[padding:4bytes] 40

此时,用于占位的14个字节是浪费的,这个对象一共使用了40个字节的内存空间。但是,如果用上面的规则对这些对象重新排序,其内存结果会变成下面这个样子:

1
2
3
4
5
6
7
8
[HEADER: 8bytes]  8
[e:      8bytes] 16
[c:      4bytes] 20
[a:      1byte ] 21
[d:      1byte ] 22
[padding:2bytes] 24
[f:      4bytes] 28
[padding:4bytes] 32

这次,用于占位的只有6个字节,这个对象使用了32个字节的内存空间。

因此,对象内存布局的第二个规则是:

规则2:类属性按照如下优先级进行排列:长整型和双精度类型;整型和浮点型;字符和短整型;字节类型和布尔类型,最后是引用类型。这些属性都按照各自的单位对齐。

现在我们知道如何计算一个继承了Object的类的实例的内存大小了。下面这个例子用来做下练习: java.lang.Boolean。这是其内存布局:

1
2
3
[HEADER: 8bytes]  8
[value:  1byte 9
[padding:7bytes] 16

Boolean类的实例占用16个字节的内存!惊讶吧?(别忘了最后用来占位的7个字节)。

继承其他类的子类的内存布局

JVM所遵守的下面3个规则用来组织有父类的类的成员。对象内存布局的规则3如下:

规则3:不同类继承关系中的成员不能混合排列。首先按照规则2处理父类中的成员,接着才是子类的成员。

举例如下:

1
2
3
4
5
6
7
8
9
classA {
   longa;
   intb;
   intc;
}
classB extendsA {
   longd;
}

 

类B的实例在内存中的存储如下:

1
2
3
4
5
[HEADER: 8bytes]  8
[a:      8bytes] 16
[b:      4bytes] 20
[c:      4bytes] 24
[d:      8bytes] 32

如果父类中的成员的大小无法满足4个字节这个基本单位,那么下一条规则就会起作用:

规则4:当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话,就必须扩展到4个字节的基本单位。

举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
classA {
   bytea;
}
classB {
   byteb;
}
[HEADER: 8bytes]  8
[a:      1byte 9
[padding:3bytes] 12
[b:      1byte ] 13
[padding:3bytes] 16

注意到成员a被扩充了3个字节以保证和成员b之间的间隔是4个字节。这个空间不能被类B使用,因此被浪费了。

最后一条规则在下面情况下用来节省一些空间:如果子类成员是长整型或双精度类型,并且父类并没有用完8个字节。

规则5:如果子类第一个成员是一个双精度或者长整型,并且父类并没有用完8个字节,JVM会破坏规则2,按照整形(int),短整型(short),字节型(byte),引用类型(reference)的顺序,向未填满的空间填充。

举例如下:

1
2
3
4
5
6
7
8
9
classA {
  bytea;
}
classB {
  longb;
  shortc; 
  byted;
}

其内存布局如下:

1
2
3
4
5
6
7
[HEADER: 8bytes]  8
[a:      1byte 9
[padding:3bytes] 12
[c:      2bytes] 14
[d:      1byte ] 15
[padding:1byte ] 16
[b:      8bytes] 24

在第12字节处,类A“结束”的地方,JVM没有遵守规则2,而是在长整型之前插入一个短整型和一个字节型成员,这样可以避免浪费3个字节。

数组的内存布局

数组有一个额外的头部成员,用来存放“长度”变量。数组元素以及数组本身,跟其他常规对象同样,都需要遵守8个字节的边界规则。

下面是一个有3个元素的字节数组的内存布局:

1
2
3
4
5
[HEADER: 12bytes] 12
[[0]:     1byte ] 13
[[1]:     1byte ] 14
[[2]:     1byte ] 15
[padding: 1byte ] 16

下面是一个有3个元素的长整型数字的内存布局:

1
2
3
4
5
[HEADER: 12bytes] 12
[padding: 4bytes] 16
[[0]:     8bytes] 24
[[1]:     8bytes] 32
[[2]:     8bytes] 40

内部类的内存布局

非静态内部类(Non-static inner classes)有一个额外的“隐藏”成员,这个成员是一个指向外部类的引用变量。这个成员是一个普通引用,因此遵守引用内存布局的规则。内部类因此有4个字节的额外开销。

最后的一点想法

我们已经学习了在32位Sun JVM中如何计算Java对象的shallow size。知道内存是如何组织的有助于理解类实例占用的内存数。


英文原文:Code Instructions,翻译:ImportNew - 郑雯

译文链接: http://www.importnew.com/1305.html


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/330073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java版大顶堆的实现

堆的概念 堆是一棵完全二叉树,一般使用数组来存储。通俗来讲堆其实就是利用数组来维护一个完全二叉树。 按照堆的特点可以把堆分为大顶堆和小顶堆 大顶堆:堆的每个结点的值都大于或等于其左右孩子结点的值 小顶堆:堆的每个结点的值都小于或…

Java 8新特性探究(二)深入解析默认方法

转载自 Java 8新特性探究(二)深入解析默认方法 什么是默认方法,为什么要有默认方法 简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。 为什么要有这个特性&am…

把本地库推送到github远程库

【1】 github上创建远程库 注意 ,远程库的名字要与本地库相同 【2】新建github远程库别名origin 【3】 代码提交 git add ./* : 把修改内容添加到暂存区 ; git commit -m msg : 提交暂存区的修改内容到本地库; g…

react antd confirm content list_React造轮系列:对话框组件 - Dialog 思路

React造轮系列:对话框组件 - Dialog 思路对话框一般是我们点击按钮弹出的这么一个东西,主要类型有 Alter, Confirm 及 Modal, Modal 一般带有半透明的黑色背景。当然外观可参考 AntD 或者 Framework 等。确定 APIAPI 方面主要还是要参考同行,…

Spring IOC 和 AOP 概览

IOC(控制反转) IoC(Inversion of Control,控制倒转)。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。 在没有IOC时,我们通过new 等关键字等方…

Java 并发实践 — ConcurrentHashMap 与 CAS

转载自 Java 并发实践 — ConcurrentHashMap 与 CAS最近在做接口限流时涉及到了一个有意思问题,牵扯出了关于concurrentHashMap的一些用法,以及CAS的一些概念。限流算法很多,我主要就以最简单的计数器法来做引。先抽象化一下需求:…

git rebase命令(转)

转自&#xff1a; https://www.yiibai.com/git/git_rebase.html git rebase命令在另一个分支基础之上重新应用&#xff0c;用于把一个分支的修改合并到当前分支。 使用语法 git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>][<u…

python tkinter计算器实例_python -Tkinter 实现一个小计算器功能

原博文 2017-03-25 22:08 − 文章来源&#xff1a;http://www.cnblogs.com/Skyyj/p/6618739.html 本代码是基于python 2.7的 如果是对于python3.X 则需要将 tkinter 改为Tkinter 将tkMessagebox&... 相关推荐 2019-12-10 15:59 − python GUI编程(Tkinter) Python 提供了多…

Spring IOC 容器启动、Bean生命周期详解

前言 在Spring IOC 和 AOP 概览中&#xff0c;简要介绍了IOC容器和AOP&#xff0c;没有深入IOC容器Bean的实例化&#xff0c;此文承接上文深入分析Bean的实例化过程、生命周期。 Spring IOC的过程 Spring的IoC容器在实现控制反转和依赖注入的过程中,可以划分为两个阶段: 容…

java 8 Lambda 表达式(副作用)

【1】转自&#xff1a; https://www.cnblogs.com/linlinismine/p/9283532.html 早在2014年oracle发布了jdk 8,在里面增加了lambda模块。于是java程序员们又多了一种新的编程方式&#xff1a;函数式编程&#xff0c;也就是lambda表达式。我自己用lambda表达式也差不多快4年了&am…

Java NIO:浅析I/O模型

转载自 Java NIO&#xff1a;浅析I/O模型也许很多朋友在学习NIO的时候都会感觉有点吃力&#xff0c;对里面的很多概念都感觉不是那么明朗。在进入Java NIO编程之前&#xff0c;我们今天先来讨论一些比较基础的知识&#xff1a;I/O模型。下面本文先从同步和异步的概念 说起&…

ubuntu安装python3.8_Ubuntu 16.04 安装 python3.8

Ubuntu 16.04 amd64 (64bit)&#xff08;纯净版&#xff09; 自带python2.7和python3.5 执行"whereis python"查看当前安装的python [rootroot ~]# whereis python python: /usr/bin/python2.7 /usr/bin/python /usr/lib/python2.7 /usr/lib64/python2.7 /etc/python…

Spring IOC 如何解决循环依赖?

前言 假设对象A、B 之间相互依赖&#xff0c;Spring IOC是如何解决A、B两个对象的实例化的&#xff1f;答案是三级缓存。 三级缓存 SpringIOC 通过三级缓存来解决循环依赖问题&#xff0c;三级缓存指的是三个Map&#xff1a; singletonObjects&#xff1a;一级缓存&#xf…

pythondocx模板_使用python-docx-template修改word文档

由于最近工作中需要自动修改word文档&#xff0c;并生成PDF文件&#xff0c;经过查阅资料后发现使用python-docx-template可以完成对word的修改工作&#xff0c;于是记录一下使用方法。文章内容大部分来自对以下博客的整理和学习https://blog.csdn.net/weixin_42670653/article…

面试必问的 CAS ,要多了解

转载自 面试必问的 CAS &#xff0c;要多了解前言 CAS&#xff08;Compare and Swap&#xff09;&#xff0c;即比较并替换&#xff0c;实现并发算法时常用到的一种技术&#xff0c;Doug lea大神在java同步器中大量使用了CAS技术&#xff0c;鬼斧神工的实现了多线程执行的安全性…

MySQL 对于千万级的大表要怎么优化?

很多人第一反应是各种切分&#xff1b; 我给的顺序是: 第一 优化你的sql和索引&#xff1b; 第二 加缓存&#xff0c;memcached,redis&#xff1b; 第三 以上都做了后&#xff0c;还是慢&#xff0c;就做主从复制或主主复制&#xff0c;读写分离&#xff0c;可以在应用层做&…

MySQL元数据库——information_schema

转自&#xff1a; https://www.cnblogs.com/postnull/p/6697077.html 平时使用MySQL客户端操作数据库的同学&#xff0c;只要稍微留神都会发现&#xff0c;除了我们建的库之外&#xff0c;还经常看到三个数据库的影子&#xff1a; 1. information_schema 2. performance_sche…

mysql 表字段信息从一张表迁移到另一张表_MySQL(数据库)笔记

###数据库之前通过流去操作文件保存数据库的弊端:1.执行效率低2.开发成本高3.一般只能保存小量数据4.只能保存文本数据####什么是DB- DataBase 数据库:代表文件集合####什么是DBMS- DataBaseManagementSystem 数据库管理系统(软件),用于管理保存数据的文件集合,用于和程序员进行…

GET与POST传递数据的最大长度能够达到多少

各种web开发语言中&#xff0c;各个页面之间基本都会进行数据的传递&#xff0c;web开发里面比较常用的数据传递方式有get post&#xff0c;一直以来我都只知道get传递的数据量要比post传递的数据量要少&#xff0c;所以传递大数据量还是要用post&#xff0c;但是 get post 这两…

maven命令实战

【1】 创建maven项目 1&#xff09;目录结构 mavenhello09|---src|---|---main|---|---|---java|---|---|---resources|---|---test|---|---|---java|---|---|---resources|---pom.xml 目录结构说明&#xff1a; main/java&#xff1a;主程序&#xff1b;main/resources&…