新建对象过程
2022-10-27 14:24:10 0 举报
AI智能生成
Java 创建对象
作者其他创作
大纲/内容
对象的组成包含三部分:对象头、实例数据、对齐填充。
Java的对象头由以下三部分组成:MarkWord、指向类的指针、数组长度(只有数组对象才有)
MarkWord包含:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。
MarkWord记录了对象锁相关的信息。当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和MarkWord有关。MarkWord在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
锁升级流程:1、当对象没有锁时,这就是一个普通的对象,MarkWord记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。锁状态为无锁。2、当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。3、当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,MarkWord中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。4、当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是MarkWord中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把MarkWord里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。5、偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁MarkWord的指针,同时在对象锁MarkWord中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把MarkWord中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。6、轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。7、自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。
MarkWord
Java对象的类数据保存在方法区。该指针在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
指向类的指针
只有数组对象保存了这部分数据。该数据在32位和64位JVM中长度都是32bit。
数组长度
对象头
对象的实例数据就是在Java代码中能看到的属性和他们的属性值。
实例数据
因为JVM要求Java的对象占的内存大小应该是8bit的倍数,所以后面有几个字节用于把对象的大小补齐至8bit的倍数,没有特别的功能。
对齐填充
对象的组成
最常见的也是最简单的创建对象的方式,通过这种方式我们可以调用任意的构造函数(无参的和有参的)去创建对象。
1、new关键字
// 方法1 User user1 = (User)Class.forName(\"com.joker.pojo.User\").newInstance(); // 方法2 User user2 = User.class.newInstance();事实上Class的newInstance方法内部调用的是Constructor的newInstance方法。
通过Java的反射机制使用Class类的newInstance方法来创建对象。这个newInstance方法调用无参的构造器创建对象。
2、Class类的newInstance方法
Constructor<User> constructor = User.class.getConstructor(Integer.class); User user3 = constructor.newInstance(123);
通过Java的反射机制使用Constructor类的newInstance方法来创建对象。java.lang.relect.Constructor类里的newInstance方法比Constructor类的newInstance方法更加强大些,我们可以通过这个newInstance方法调用有参数的和私有的构造函数。
3、Constructor类的newInstance方法
@Datapublic class User implements Cloneable { private String id; private String userName; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } public static void main(String[] args) throws CloneNotSupportedException { User user = new User(); User user1 = (User)user.clone(); }}
通过实现Cloneable接口,重写Object类的clone方法来创建对象(浅拷贝)。Java为所有对象提供了clone方法(Object类),又出于安全考虑,将它设置为了保护属性。protected native Object clone() throws CloneNotSupportedException;我们可以通过反射(reflect)机制在任意对象中调用该方法。如果不通过反射的方式,我们要如何实现对象克隆呢?可以通过实现Cloneable接口,重写Object类的clone方法来实现对象的克隆实现原理:Java API采用判断是否实现空接口Cloneable的方法来判断对象所属的类是否支持克隆。如果被克隆对象所属类没有实现该接口,则抛出NotDeclareCloneMethod 异常。当支持克隆时,通过重写Object类的clone方法,并把方法的修饰符改为public,就可以直接调用该类的实例对象的clone方法实现克隆。
4、Object类的clone方法
public static void main(String[] args) throws Exception { User user = new User(); user.setId(\"1\"); user.setUserName(\"haha\"); // 写对象 ObjectOutputStream output = new ObjectOutputStream( new FileOutputStream(\"F:\\\\joker\\\\text.txt\")); output.writeObject(user); output.close(); // 读对象 ObjectInputStream input = new ObjectInputStream(new FileInputStream( \"F:\\\\joker\\\\text.txt\")); User user1 = (User) input.readObject();}
当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。
5. 反序列化
新建对象的方式
new关键字时创建对象时,首先会去运行时常量池中查找该引用所指向的类有没有被虚拟机加载,如果没有被加载,那么会进行类的加载过程。类的加载过程需要经历:加载、链接、初始化三个阶段。
1、检查类是否已经被加载
2、为对象分配内存空间
分配完内存后,需要对对象的字段进行零值初始化(赋默认值),对象头除外。零值初始化意思就是对对象的字段赋0值,或者null值,这也就解释了为什么这些字段在不需要进程初始化时候就能直接使用。
3、为对象的字段赋默认值
对这个将要创建出来的对象,进行信息标记,包括是否为新生代/老年代,对象的哈希码,元数据信息,这些标记存放在对象头信息中
4、设置对象头
init方法包含成员变量、构造代码块的初始化,按照声明的顺序执行。
5、执行实例的初始化方法init
6、执行构造方法
步骤
新建对象过程
0 条评论
回复 删除
下一页