难难难难难难难!美团追魂七连问!关于Object o = new Object()创建对象的问题
一,请解释一下对象的创建过程?(半初始化)
第一步,申请空间,设置默认值
第二步,调用构造方法,设置初始值
第三步,建立关联(对象名称和对象地址的引用)
二,加问DCL单例(Double Check Lock)要不要加volatile问题?(指令重排)
引入一个新问题:java代码一定是按照顺序执行的吗?答案是不一定!
例如:
int x=1;
int y=1;
这两行代码,也有可能是先执行第二行,再执行第一行,这就是指令重排序,cpu为了提高执行效率,进行重排序(cpu会判断是否有指令重排的条件,只要能保证最终结果的一致性,就可以指令重排)
例如:
int x=1;
x++;
这两行代码,就不会指令重排序,因为第二行代码对第一行代码进行了依赖,重排序逻辑就乱了
再结合问题一(对象的创建过程)看下面的这段代码,经典的单例模式,如果10000个线程同时执行下面这段代码,会不会有什么问题?
答案揭晓:肯定有问题
看下图,比如thread1第一次进来,判断是否为空,为空,拿到锁之后创建对象,但是创建对象有三步(申请空间,初始化,关联),但是这里发生了指令重排序,申请了空间,并进行了关联,但此刻并未初始化(对象有了,数据为空),此时thread2进来了,判断是否为空,不为空,不为空的话,直接就拿走对象用了,其实这个对象的数据都是未初始化的数据,因此就错了。
volatile作用:保证线程之间的可见性&&禁止指令重排序
三,对象在内存中的存储布局?(对象与数组的存储不同)
对象在内存中的存储布局
markword
占8个字节,王八的屁股:规定,只要是64位的虚拟机,都是8个字节。
类型指针
默认压缩,占4个字节,不压缩占8个字节,实例数据有多少就占多少,类型指针指向创建的类,此处指针指向T.class
对齐
:如果占用的总内存能被8整除,例如16字节,就不做处理,如果不能被8整除,例如18,则需要补位到8整除的子节数,18 > 24,补充六个子节,那么这个类就占用了内存共24个字节。
四,对象头具体包括什么?(markword klasspointer)
第七个问题解决之后,再来看这个问题
对象头包括markword标记位和类型指针两部分。
markword包含了三大信息(锁信息、hashcode、GC信息)
先来看锁信息
public class study {
private static class T{
}
public static void main(String[] args) {
T t = new T();
//原始状态
System.out.println(ClassLayout.parseInstance(t).toPrintable());
//加锁
synchronized (t){
System.out.println(ClassLayout.parseInstance(t).toPrintable());
}
}
}
再来看hashcode信息
public class study {
private static class T{
}
public static void main(String[] args) {
T t = new T();
//原始状态
System.out.println(ClassLayout.parseInstance(t).toPrintable());
t.hashCode();
System.out.println(ClassLayout.parseInstance(t).toPrintable());
}
}
最后GC信息,不太容易能看见,垃圾回收时的三色标记(黑白灰),markword有标记颜色的标记位子。
五,对象怎么定位?(直接 间接)
什么叫对象定位?例如,String s = new String() 创建了一个string对象,放在了堆里面,s存对象的地址引用,定位就是s引用如何找到这个对象。
一共两种方式,直接定位(直接指针)&&间接定位(句柄方式)hotspot默认使用直接定位。
直接指针:直接定位到x字节到y字节的连续字节,找到类,优点:直接访问,缺点:GC需要移动对象的时候稍麻烦,因为字节顺序变了之后,t引用就找不到对象原地址了,还得配合修改地址。
句柄方式:对象小,垃圾回收时不用频繁改动t 缺点:两次访问
六,对象怎么分配?(栈上 - 现成本地 - Eden - Old)
看图说话
- 创建一个对象,通过逃逸分析,看是否可以分配在栈上,如果可以分配到栈上,就分配到栈上
- 分配到栈上的好处是,方法执行完随着栈帧的弹出,对象也随之消亡,不用GC回收
- 如果不可以分配到栈上,看对象是否够大,如果够大,直接放在堆区的老年代(对象大小的标准可以通过jvm参数进行配置),如果不够大,则放在伊甸园区
- 放在伊甸园区之前还有一步,就是TLAB,多个工作线程并发工作(把对象放在伊甸园区),存在并发问题,所以每个工作线程都有自己独立的空间,优先把对象存在自己独立的空间中,如果空间不够再从伊甸园中取内存,这样就不存在并发问题了,效率更高了
- 伊甸园区进行GC垃圾回收,垃圾对象直接消亡腾出内存,非垃圾对象进入S1存活区,再次GC时,伊甸园区和S1存活区的垃圾对象直接消亡,非垃圾对象全复制到S2对象中,伊甸园区和S1区删除垃圾对象,清空,重新存储新来的对象,如此往复,S1 > S2 && S1 < S2
七,Object o = new Object() 在内存中占用多少子节?
看了第三个问题(对象在内存中的存储布局)之后,再回到这个问题
这里进行测试,创建一个类,然后使用JOL工具对这个类进行打印
使用方式如下
ava openjdk 提供jol 工具,可以查看class的头信息
下载 jol 工具包
https://repo.maven.apache.org/maven2/org/openjdk/jol/jol-cli/
选择一个版本,进去后下载 jol-cli-.-full.jar 一定要下载full 的jar 导入包。
打印:
System.out.println(ClassLayout.parseInstance(byte.class).toPrintable());
public class study {
private static class T{
}
public static void main(String[] args) {
T t = new T();
System.out.println(ClassLayout.parseInstance(t).toPrintable());
}
}
打印结果
com.ftx.camel.study$T object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
加了一个变量之后
public class study {
private static class T{
int m;
}
public static void main(String[] args) {
T t = new T();
System.out.println(ClassLayout.parseInstance(t).toPrintable());
}
}
又加了一个字符串变量
public class study {
private static class T{
int m;
String s = "xxxxxxxxxxxxxxx";
}
public static void main(String[] args) {
T t = new T();
System.out.println(ClassLayout.parseInstance(t).toPrintable());
}
}
上面的16字节加上字符串4个字节等于20字节,对齐之后(被8整除进行补位)24字节
有同学想问,字符串的长度不一样长,为何字节一直是4个字节?因为类中只存了字符串的地址,并没有存字符串!
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~