文章目录
- 1.对象结构
- 2.扩展补充
- 3.小结
1.对象结构
在介绍之前.先来看一个java高频面试题,new String(''hello") 创建了几个对象?
1.这里分情况讨论,如果hello已经在
常量池中存在,那么就是在堆中创建1个对象
,并返回堆中对象的引用。
2.如果对象hello不在常量池中,那么就是2个对象
,分别在堆和常量池(1.8之后字符串常量池在堆中,之前在方法区
)创建一个对象并返回堆对象的引用。
(1)对象分为对象头、实例数据、字节填充。
一个对象实例数据是放在堆中,对象的引用时放在栈中,对象所引用的class类对象是在方法区(JDK1.8之后为元空间)。
1)对象头包含MarkWord、类型指针、数组长度
这三块。
其中MarkWord
在不同位操作系统中存储大小和方式不同,32位操作系统采用以下结构表示:
64位操作系统采用以下结构:
其中MarkWord和类型指针占用字节和操作系统一样,32位系统4个字节,64位系统8个字节。
这个对象头中数组长度占用4个字节,是创建数组对象特有的一块内存分配。
下面简要介绍下对象头的作用。
1对象头MarkWord记录了对象分代年龄,锁,线程id,hashcode信息等,为什么这么设计呢?【大家可以思考思考】
1.这里就涉及到Java并发多线程锁的实现和JVM中垃圾回收。
其中锁标识位、是否偏向锁、线程ID、等指针都用来多线程并发锁状态更新记录。
众所周知,Java内部有一个锁关键字synchronized
,这个是一个可重入内置锁,JDK在1.6以后对其使用做了内置化升级,就是启用了锁升级机制
来在保证线程安全下提升并发效率。多线程情况下,通常刚进来时,第一个线程获取锁对象时, 这个对象会从无锁状态变成持有偏向锁
。随着后续线程竞争获取锁时,如果上个线程没执行完,则会立马回到savepoint暂停执行,锁直接升级轻量级锁,cpu轮询时间切片执行其他线程,如果上个线程执行很快,立马执行完,释放了锁,那么此时线程获取锁仍然是偏向锁。偏向锁性能相对较高,一旦升级为轻量级锁,当后续涌入大量线程发生竞争获取锁时候,会不断自旋获取锁,当自选周期达到500个时钟周期,或者极端情况下,轻量级锁已经无法管理整个线程运行情况时,此时会升级为重量级锁。此时线程就几乎变成串行同步执行了,没有获取锁的线程直接陷入阻塞【线程上下文切换非常损耗CPU性能】,这个状态下性能最低。
分代年龄用于JVM进行内存回收时,是否判断当前对象是否进入年老代。默认是15
2.讲完了MarkWord,那么对象头类型指针干吗用的呢?
用于反射构建对象,还有就是调用类的方法/属性。
3. 对象头数组长度这个东西,便于操作对象使用,快速寻址,因为Java是强类型语言,根据数组对象地址以及数组的下标,可以快速定位数组某个索引下标的数据
。这也就是说所有数组大家都可以通过 obj.length
来获取长度。
2)实例数据这个就是对象实例占用的字节数,其中引用类型在64为系统占用8个字节,32位系统占用4个字节。
3)对齐填充,这个为啥子还要对齐填充呢,大家有没有思考过?
所谓对齐填充不是必须
的,所有对象需要满足是4个字节【32位系统】或8个字节【64位系统】
的整数倍。如果不够,则需要通过字节填充来满足这个规范。这个还是为了方便操作系统寻址计算
。
2.扩展补充
- 基础类型对应的包装类,提供了缓存机制,即为了减少对象的频繁创建和销毁,减少内存使用提高程序运行效率,
数值类型采用1个字节来构建缓存对象,即缓存值范围在【-128-127】。其中Float和Double不具有缓存机制。
- 缓存机制实现是基于内部一个静态内部类
包装类Cache
这种形式类 比如Integer类型 采用IntegerCache
作为缓存实现内部类,默认是-128到127,当然Java技术大佬还提供了可供开发者修改Integer缓存范围。
上面是IntegerCache源码
静态代码块部分,可见,开发可以设置环境变量来自定义Integer缓存范围。 JVM参数设置方式如下:XX:AutoBoxCacheMax=<size>或 -Djava.lang.Integer.IntegerCache.high=<high>
3.注意事项:
只有当两个包装类对象是通过调用静态工厂方法Integer.valueOf()
来实现对象创建时,才会相等。以
Integer为例子,这方法调用时,会先走缓存进行比较。
下面做个简单例子说明:
Integer a= 120;
Integer b =120;
Integer c = new Integer(100);
Integer d = new Integer(100);
Integer e =100;
int f =100;
System.out.println(a==b); //结果为true
System.out.println(c==d); //结果为false 两个不同对象,地址不同
System.out.println(c==e); //结果为false 对象比较地址,地址不同
System.out.println(c==f); //结果为true 包装类自动拆箱,比较值。
第一个结果为true,初看是两个对象,但是120赋值给Integer对象做了包装,隐式调用了Integer.valueOf(120)
在赋值给a和b;由于采用了缓存机制,因此a和b是同一份对象地址也相同
,如果Integer a=128,Integer b =128,比较的话就是false。
3.小结
本文通过new 关键字创建对象开始,简要对Java中对象的结构、存储以及对象结构每个部分的功能做了简要介绍,如讲述有误,希望大家评论区交流指正。