上篇文章后半部分提到,我们在估算1亿条整数放到内存中,会占用多大的内存的时候,仅仅按照每个Integer 32bit算了,即按照原始类型int来估算的,结果严重超出预料。
仔细想想,对象在jvm中是怎么存的呢?
首先,java对象要包含的基本数据至少要有两部分:
1、类以及超类的实例声明的实例变量;
2、指向类数据的引用,jvm需要通过此引用找到该对象的(可能存在的)方法表、类型信息。其中类型信息包括类型基本信息、常量池、字段信息、方法信息、类变量信息、指向Class的引用、指向ClassLoader的引用等。这些类数据都放在方法区。
另外,还有一些其他信息:
3、多个线程访问同一个对象时候同步用的对象锁lock,这个可以在用到的时候再lazy创建。任何一刻只能有一个线程“拥有“该对象锁。
4、wait set,让多线程为完成统一工作而协调工作用,wiat set与wait和notify方法联合使用。堆中可以为一个wait set的引用,可以在用到的时候lazy 创建。
5、跟踪gc信息,如标示方法的finalize方法是否运行过
但是jvm在堆中怎么存,jvm spec没有做详细的规定。<<深入Java虚拟机>>给出了两种可能的实现方案:
1、把堆分为两部分:一部分为句柄池,另一部分为对象池。每个实例引用都是一个指向句柄池的本地指针,句柄池由指向对象池和类数据的两个指针组成;对象池中只存实例数据。这种表示法的优点是 gc时候,清理完垃圾对象后,还要整理碎片,就需要把非垃圾对象拷贝到一个新的区域,此时由于拷贝,新对象的地址已经改变,但并不需要指向对象的引用值做改变,只用改变句柄池中指向对象池的引用地址即可;缺点是每次访问对象都需要经过两次指针跳转,效率较低。
2、对象指针指向一组数据,改组数据本身包含有实例数据和指向类数据的指针。优缺点跟第一种方案相反。
通过以上分析可得,Integer在内存中有一个指向方法区里边类信息的指针,这个指针占用4bytes;另外Integer中实例变量只有一个int类型的字段,所以为32位,4bytes。在不考虑lock、wait set、gc相关信息占用的时候,如果是第一种方案,有4bytes的指向对象池的指针,一共是3*4=12bytes;如果是第二种实现方案,则是2*4-8bytes的指针。我们怎么能确定jvm用的是第一种方案还是第二种方案呢?lock、wait set、gc相关信息在仅仅创建,累加,销毁的时候是否的确不存在呢?
从VisualVM生成的heap dump文件分析,每个Integer占用了3*4bytes:
从Heap Analyzer上显示信息看就更直接了当了:
但是怎么能确定那额外的根据上边计算,Integer中布确定的4bytes的空间到底是存了什么呢?这个我们还需后续继续深究
继续上篇文章分析:
既然1个Integer占用12bytes,那个1亿条记录也就占用1.2G,加上HashMap中key和value都存的是Integer,一共算上也就2.4G吧,也不应该2kw条记录占了近1g的记录啊。我们是不是漏掉了什么?
的确,我们漏掉了对HashMap本身的分析,从下图看,我们忘记了HashMap$Entry,那一个键值对(即一个Entry)占用多少个字节呢?
要回答这个问题,我们需要分析下Entry的源码了,至少看看它的实例变量吧:
从图中看,有四个实例变量,另外别忘了还有一个指向方法区类信息的指针,一共5*4=20bytes,对么?来看看heap anyalyzer打开的heap dump中的信息:
size为24bytes,跟Integer一样,多出了4bytes,这个是从哪里来的呢?后续文章跟踪分析!另,注意那个total size是指这个Entry本身以及可以到达的所有对象占用的内存空间,此处包括两个Integer,即 key和value占用的空间,为48bytes(Total size :The subtree size of an object is the sum of its size and the sizes of all the objects that it reached from its children. Note that each object is assigned a unique parent and root during )
如此计算下来,1亿整数放入到HashMap中记录每个出现的次数,粗略估算应该占用:48*1亿 = 4.8 * 10亿 = 4.8*2^30 =4.8G,2kw整数也需要占用近1g内存,粗略估算跟实际基本吻合了。
但是从heap dump分析结果看,这100w记录占用空间是56m,而不是48bytes * 100w = 48M,那8m空间是做什么用了呢?HashMap中空的没有使用的空间用了?
至于上边多处关于对象额外4bytes到底存的什么数据的问题,以及100w占用56M而不是48M的问题,后续接着分析!
分享到:
相关推荐
- 对于超出这个范围的 `Integer` 值,JVM 会创建新的对象实例。 - 示例代码展示了 `Integer` 类型在不同范围内的行为差异。 - **多态性示例:子类方法重写** - 当在父类中调用一个方法时,如果该方法在子类中被...
Java 虚拟机(JVM)是Java编程语言的核心组成部分,它为Java应用程序提供了一个跨平台的运行环境。JVM的设计目标是实现Java代码的“一次编写,到处运行”原则,这得益于它的平台无关性和字节码执行机制。 1. **JVM...
当 JVM 得到一个 Java 字节码应用程序后,便为该代码中一个类的每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息: * 局部变量:是被组织为一个以字长为单位、从 0 开始计数的数组。...
- **`boolean`**: 占用1个字节(实际内存占用可能更多),只能取`true`或`false`。 这些基本类型在JVM中被高效地处理,它们的操作通常在栈中完成,从而提供快速的访问和计算速度。此外,每种基本类型都有相应的包装...
42、一个“.java”源文件中是否可以包含多个类(不是内部类)?有什么限制? 12 43、说出一些常用的类,包,接口,请各举5 个。 12 44、Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类?是否可以...
- `byte`:字节型,占用1个字节。 - `short`:短整型,占用2个字节。 - `int`:整型,最常用,占用4个字节。 - `long`:长整型,占用8个字节。 - `float`:单精度浮点型,占用4个字节。 - `double`:双精度浮点型,...
在构造器中,this()可以用来调用同一个类中的其他构造器,而super()则用来调用父类的构造器。如果在子类的构造器中没有显式使用this()或super(),Java编译器会默认调用父类的无参构造器super()。 2. 作用域public, ...
Java中的字符串池是指Java虚拟机(JVM)中的一块特殊内存区域,用于存储字符串常量。在Java中,每个字符串常量都将被存储在字符串池中,以便重用和提高性能。 5. 字符串在Java中是不可变的还是最终的?如果是,那么...
1、一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 2、Java有没有goto? 3、说说&和&&的区别。 4、在JAVA中如何跳出当前的多重嵌套循环? 5、switch语句能否作用在byte上,能否作用在long上...
每个Java源文件编译后会生成字节码文件(.class),这些字节码文件由JVM解释执行。 - **跨平台特性**:Java之所以被称为跨平台的编程语言是因为其基于JVM的设计。无论在哪个操作系统上(如Windows、Linux或Mac OS),...
在Java API实例中,我们可以找到以下关键知识点: 1. **基础类和接口**:如`String`、`Integer`、`ArrayList`、`HashMap`等,这些都是Java开发中最常用的类。`String`用于处理文本,`Integer`提供整数操作,`...
在数组中,每个`boolean`元素占用1个字节。 - **字符型** - `char`:2字节(16位),默认值为`\u0000`(null字符),封装类为`Character`。 #### 标识符的命名规则 - 标识符可以包含英文字母、数字(0-9)、...
instanceof是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例。 Java自动装箱与拆箱 装箱:自动将基本数据类型转换为包装器类型(int-->Integer),调用方法:Integer的valueOf(int)。 拆箱:自动将...
### Java语言特点 1. **简单易学**:Java语言的语法与C语言和C++...Java中的`boolean`类型虽然有自己的字面量`true`和`false`,但在JVM中,其操作会被编译为对`int`类型的处理,这一点在处理`boolean`数组时尤其明显。
Java不支持C++中的复制构造方法,即一个类的实例直接初始化另一个同类型的实例。Java通过提供克隆(clone)方法来实现类似的功能,但这需要对象实现Cloneable接口并覆盖Object类的clone方法。 7. 接口与抽象类: ...