Java的自动内存管理机制(automatic storage management system known as a garbage collector)省却了很多编码工作,大大地提高了Java的生产力,而且JVM的性能也越来越好,特别是G1的出现,改善了垃圾回收中stop the world的状况。
也许很多人都没有考虑过这个问题,new一个Object对象到底占用多少内存呢( Object obj = new Object() )?
这里很明确的是obj是一个指向对象的引用(reference - there are three kinds of reference types: class types,array types, and interface types),引用的长度决定了Java的寻址能力,32位的JDK是4字节,64位的JDK是8字节(指针未被压缩的情况下)。
因为obj对象没有任何数据(field),会在堆上为它分配空间吗?如果分配空间,里面存储了什么内容?
以面向对象的思维来分析,对象封装了数据和行为,是一个统一的整体,虽然obj对象没有数据,但是有行为(Object类定义了12个方法)。当我们执行完new操作后,obj的值为堆内存的地址,既然obj都指向一块内存了,说明是会在堆上为其分配空间的。
那么分配的空间有多大,存储了什么内容呢?在The Java Virtual Machine Specification Java SE 7 Edition和The Java Language Specification Java SE 7 Edition里面没有找到相关的描述,这很可能是属于JVM实现自由控制的范畴了。我们可以利用JDK自带的工具jvisualvm.exe来查看分配的空间有多大。为了方便在jvisualvm中查看对象占多少内存,这里使用一个私有的静态内部类EmptyObject来替代Object,因为类定义为空,所以可以等同对待EmptyObject和Object。
/** * 构造一个无任何字段的空对象占多少内存 * @author 杨尚川 */ public class EmptyTest { public static void main(String[] args) throws InterruptedException{ //加到集合中,使垃圾无法回收 List<EmptyObject> emptys = new ArrayList<>(); for(int i=0;i<100;i++){ emptys.add(new EmptyObject()); } //打开jvisualvm,查看EmptyObject的大小为16字节 Thread.sleep(60*1000); } private static class EmptyObject{} }
我们在这里面通过new不同的对象数(for循环次数),来分析内存占用,new 1个对象是16字节,new 2个对象是32字节,new 100个对象是1600字节,通过很多次的尝试,我们从jvisualvm里面可以看到 字节数=对象数*16 ,我们有理由相信对象数跟字节数的线性关系。从这里可以看出,jvisualvm显示的内存占用跟引用的4字节或8字节是没有关系的,也就是说,jvisualvm显示的是堆内存占用,这也很好理解,毕竟所有引用的字节占用是固定的。8字节是引用,16字节是堆内存,总共是8+16=24字节,所以new一个Object对象占用8+16=24字节(64位JDK)。
如果JDK是32位,按如上分析方法可知new一个Object对象占用4+8=12字节(32位JDK),如下图所示:
64位JDK:
32位JDK:
那么分配的16字节(8字节)的堆内存中存储了什么内容呢?当我们Object obj = new Object();的时候,在栈内存中有一个引用obj,他可能是32位也可能是64位,obj实质只是一个内存地址,那么当我们调用obj.xxx()的时候,JVM怎么知道obj是哪个类的实例呢?所以,可以大胆地推测,obj对象的16字节(8字节)的堆内存中记录了对象属于哪个类的信息,问题是这16字节(8字节)的结构是什么样的?不清楚!
不过我们仍然可以大胆地猜测一下,通过上面的64位和32位的堆内存大小对比分析发现,堆内存分配的大小是引用的两倍,上面我们已经猜测堆内存中会记录对象是哪个类的实例,如何记录呢?因为类对象是放置在方法区的,类对象本身也是一个对象,因此可以通过一个引用指向它,所以堆内存有可能就是放置了两个引用,指向两个对象。分析到这里,事情就比较明朗了,堆内存中可能就放置了两个内存地址,一个指向EmptyObject.class(在实验代码中用EmptyObject来代替Object),一个指向什么呢?不清楚!
在Inside the Java 2 Virtual Machine 2nd by Bill Venners的5.3.5中有这么一段描述:
The Java virtual machine specification is silent on how objects should be represented on the heap. Object representation--an integral aspect of the overall design of the heap and garbage collector--is a decision of implementation designers.
好了,事情最终清楚了,JVM规范并没有规定Java对象在堆中是如何表示的,对象的表示是堆和垃圾收集器的整体设计的一个组成部分,这是由JVM实现的设计师来决定的。 因此,如果我们真的想搞清楚对象是如何表示的,那么需要查询HotSpot VM或是其他JVM实现的相关资料。
在淘宝工程师莫枢(撒迦)的《JVM分享》PPT的第112页介绍了“HotSpot中的Java对象布局”,这真是现在关心的内容,通过PPT的介绍说明前面的猜测是对的,如下图所示:
我们研究new一个Object对象占多少内存可能没什么实际意义,因为我们在编程的时候就可以确定对象树,基本可以确定对象大小,除了变长字段,当然,变长字段我们一般也会有长度限制。所以我们真正关心的是所有数据最终的大小,也就是数据库的大小。
面向对象的分析、设计和编程都把“封装”奉为圭臬,“分层”更是架构设计中至关重要的设计准则。因为只有这样,才能实现基本的解耦、让协作分工成为可能,满足工业要求的最大化生产力的最终目标。
那么这种没有什么实际意义的问题为什么要研究呢?我觉得只能用三个字来形容:好奇心。好奇心是驱使我们研究技术的强大推力,当我们工作了很多年,尤其是在不重视技术的公司,我们对技术还有激情吗?保持一颗敏感好奇的心,也许技术之路可以走的更长更远。
这篇文章的重点是展示一种分析问题的思路,要大胆猜测,小心求证,追本溯源,引经据典。求证方式:查找标准规范、查找经典权威书籍、自己做实验、查找源代码等。
参考资料:
1、Java™ Virtual Machine Technology
2、The Java Virtual Machine Specification Java SE 7 Edition
3、The Java Language Specification Java SE 7 Edition
4、Inside the Java 2 Virtual Machine 2nd by Bill Venners
5、JVM分享
相关推荐
new一个Object对象占用多少内存?-附件资源
此外,JOL库(Java Object Layout)是一个非常有用的工具,可以帮助我们分析JVM中对象的内存布局。通过引入该库,可以方便地获取对象和数组的详细内存占用信息。 总之,Java对象和数组的内存占用取决于JVM的配置、...
3. 何时释放:当对象的引用计数变为0时,系统会自动调用`dealloc`方法,释放对象占用的内存。 四、避免内存泄漏 1. 避免循环引用:两个对象相互引用会导致它们都无法被释放。使用弱引用或无主引用打破循环。 2. ...
在Java编程语言中,了解一个对象占用的内存大小对于优化程序性能、理解内存消耗以及防止内存泄漏至关重要。本文将深入探讨如何计算Java对象所占内存,并通过提供的代码示例进行详细解析。 首先,我们需要理解Java...
一个空的String对象占用28个字节的内存,包含了一个指向字符数组的引用、一个偏移量、一个字符的长度和一个哈希码。当字符串内容非空时,内存占用会增加,因为需要存储字符数据。例如,字符串"ab"会占用28 + 2 * 2 =...
1. **内存分配**:当使用`new`关键字创建对象时,JVM会在堆上为新对象分配一块内存空间,并返回一个指向这块内存空间的引用。例如: ``` new Color(100, 100, 120); ``` 这里`new`运算符负责为对象分配内存空间...
当一个对象不再被引用时,GC会自动回收该对象占用的内存,防止内存泄漏。 在Java中,对象的创建过程涉及内存分配和初始化。当我们使用`new`关键字创建对象时,首先在堆内存中为对象分配空间,然后调用构造函数初始...
1. **创建解析器**: 首先,我们需要创建一个`DocumentBuilderFactory`实例,然后通过它获取一个`DocumentBuilder`对象,该对象可以用来解析XML文件。 ```java DocumentBuilderFactory factory = ...
当你不再需要一个对象时,应该使用"delete"来释放其占用的内存: ```cpp delete p; ``` 执行`delete p;`后,`Person`对象的析构函数会被调用,然后内存被回收。注意,一旦执行了`delete p;`,`p`就不再指向有效的...
3. **填充数据(Padding)**:为了对齐,如果实例数据不足一个内存对齐单位,JVM会在对象尾部添加填充字节,确保对象大小为对齐单位的整数倍。 理解对象内存布局有助于优化程序性能,例如减少对象创建、理解和使用...
这种做法可以将内存占用降低到原来的十分之一左右。 #### 避免使用变体类型以节省内存 变体类型是一种动态类型的数据结构,它可以容纳任何类型的值。然而,这使得变体类型需要较多的内存来存储数据(大约16字节)...
在 Java 堆中内存是绝对规整的,所有用过的内存都放在了一边,空闲的内存放在另外一边,中间放一个指针作为分界点的指示器,那所分配的内存就仅仅是把那个指针向空闲的那边进行挪动一段与对象大小相等的距离。...
此时内存中的对象就似乎成为了一个无主的对象,就会被垃圾回收器销毁。不过这也有例外。如现在同一个对象有两个名字,分别为 str1 与 str2。此时若只是将 NULL 值赋值给 str1,那么内存的这个对象仍然有一个主人,即...
浅拷贝(Shallow Copy)仅仅复制对象的数据成员的值,而不复制动态分配的内存,可能导致两个对象共享同一块内存,改变一个对象会影响到另一个。深拷贝(Deep Copy)则会为每个对象分配独立的内存,确保对象的独立性...
如果类包含虚函数,VS2008会在对象中插入一个指向虚函数表的指针,用于实现多态性。这个指针通常是对象的第一个成员,占据4字节(32位系统)或8字节(64位系统)。 4. **对象头(Object Header)**: 非POD...
在 new 一个 Script Asset 时,Unity3D 会创建一个新的 Class 实例,并将其挂到主线程的调用链中。 为了优化 Unity3D 的内存使用,我们可以采取以下措施: 1. 使用 AssetBundle 加载资源,可以减少内存的使用。 2....
### Java对象池技术的原理 在Java开发领域中,对象池...通过合理地管理和重用对象,不仅可以减少内存占用,还能有效提升程序运行效率。在实际应用中,开发者可以根据具体需求灵活调整对象池的配置,以达到最佳效果。