文章目录
1 对象实例化
1.1 创建对象的方式
- new
变形1:XXX的静态方法
变形2:XXBuilder/xxFactory的静态方法
- Class的newInstance()
反射的方式,只能调用空参的构造器,权限必须是public
- Construtor的newInstance(xxx)
反射的方式,可以调用空参,带参的构造器,权限没有要求
- 使用clone()
不使用任何构造器,当前类需要实现cloneable接口,实现clone()
- 使用反序列化
从文件中,从网络中获取一个对象的二进制字节流。
1.2 字节码角度查看创建对象
代码
/**
* @program: jvmDemo
* @description:
* @author: wfg
* @create: 2021-06-18 12:10
*/
public class Test12 {
public static void main(String[] args) {
Object obj = new Object();
}
}
本人菜鸡,感兴趣的可以查看底层汇编
1.3 对象创建的步骤
-
加载类元信息
-
为对象分配内存
-
处理并发问题
-
属性的默认初始化
-
设置对象头信息
-
属性的显示初始化,代码块中初始化,构造器初始化
1.3.1 判断对象对应的类是否加载,链接,初始化
虚拟机遇到一条new指令,首先检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用的类已经被加载,解析和初始化(判断类元信息是否存在),如果没有则在双亲委派模式下,使用当前类加载器进行查找对应的.class文件。没有找到就抛出ClassNotFoundException 异常,找到则进行类加载并生成Class类对象
1.3.2 为对象分配内存
首先计算对象占用空间大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,4个字节大小。
内存分配
- 内存规整
jvm 采用的是指针碰撞,用过的内存在一边,空闲的内存在另外一边,中间放一个指针作为分界点的指示器,分配内存就是把指针向空闲那边移动一段与对象大小相等的距离。
- 内存不规整
首先jvm需要维护一个列表,记录那些内存块时可用的,在分配的时候从列表中找到一块足够空间分给对象实例,并更新列表上的内容。
- 说明
选择哪种分配方式是由java堆是否规整决定的,而java堆是否规整是由所采用的垃圾收集器是否带有压缩整理功能决定的
1.3.3 处理并发安全问题
-
采用cas失败重试,区域加锁保证更新的原子性。
-
每个线程预先分配一块tlab
1.3.4 初始化分配到的空间
所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用
1.3.5 设置对象的对象头
将对象的所属类(元数据信息),对象的hashcode 和对象的gc信息,锁信息数据存储在对象的对象头
1.3.6 执行init方法进行初始化
在java程序的视角,初始化才刚开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把对内对象的首地址赋值给引用变量。
2 对象的内存布局
2.1 对象头
主要包含两部分: 运行时元数据和类型指针,数组的话还记录了数组的长度
运行时元数据
-
哈希值(hashcode)
-
gc分代年龄
-
锁状态标志
-
线程持有的锁
-
偏向线程id
-
偏向时间戳
类型指针
指向元数据instanceKlass,确定对象所属的类型(元空间内的类元数据,主要用于判断是属于哪个类的实例)
2.2 实例数据
它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段
2.3 对齐填充
不是必须的,仅起到占位符的作用
代码
/**
* @program: jvmDemo
* @description:
* @author: wfg
* @create: 2021-06-18 15:50
*/
public class Customer {
int id =100;
String name;
Account account;
{
name="匿名客户";
}
}
class Account{
}
使用
/**
* @program: jvmDemo
* @description:
* @author: wfg
* @create: 2021-06-18 15:55
*/
public class Test13 {
public static void main(String[] args) {
Customer cust = new Customer();
}
}
3 对象访问定位
主要有两种 句柄访问与直接指针