文章目录
- 一、创建对象的步骤
- 二、类加载机制
- 三、内存分配
- 指针碰撞 (内存连续)
- 空闲列表 (内存不连续)
- 四、创建对象的5种方法
- 五、浅拷贝与深拷贝
以下一行代码内部发生了什么?
Person person = new Person();
一、创建对象的步骤
根据JLS中的规定,Java对象的创建过程可以简要地描述为以下几个步骤:类加载、分配内存、初始化、调用构造方法、返回对象应用
- 类加载:包括查找并加载类的字节码
.class
文件,并将其转换成可执行代码,使得JVM可以执行这些类。 - 分配内存:当使用new关键字创建一个对象时,Java虚拟机会在堆内存中分配一块
连续
的内存空间来存储对象的实例变量和引用类型。 - 初始化:分配内存后,Java虚拟机对新创建的对象进行初始化。这包括对对象的实例变量进行默认初始化(数值类型为0,引用类型为null),并调用相应的构造方法来完成对象的初始化过程。如果对象存在
继承关系
,会从父类到子类依次调用构造方法,确保所有父类的构造方法都得到执行。 - 调用构造方法:构造方法是特殊的方法,用于初始化对象的状态。在对象初始化的过程中JVM会调用与类对应的构造方法来完成实例变量的初始化以及其他必要的初始化操作。
- 返回对象引用:对象初始化完成后
new
表达式会返回对新创建对象的引用,使得程序可以通过该引用访问和操作对象。
二、类加载机制
JVM提供了三个类加载器,分别是BootStrap > Extension > Application,也可以自定义。
为了保证安全性和避免重复加载
,设计了双亲委派机制
。
即按照类加载器的层级关系逐层进行委派。如果父加载器无法加载,自己再尝试加载。如果已经加载过,自己就不用再重复加载。
只要是符合 JVM 规范的字节码,不管通过 Java 源码编译生成的还是使用Java 字节码操纵工具类库(ASM、Javassist、cglib)生成的,都可以交给类加载器去加载。
三、内存分配
JVM在运行Java程序时,需要管理内存的分配和回收。在内存管理的过程中,有两种常见的方式:指针碰撞(Bump the Pointer)和空闲列表(Free List)。JVM根据堆的不同区域和不同用途,选择不同的分配方式,具体的实现取决于不同的JVM实现和垃圾收集器策略。
指针碰撞 (内存连续)
常用于实现固定大小
的内存分配。JVM维护一个指向空闲内存区域的指针(称为"指针碰撞指针"),并且假定堆中的内存是连续的,分配内存就是将指针往前移动一定的字节数,然后将移动后的位置返回给请求分配内存的程序。
该方式是以堆的内存是连续(内存规整)的作为前提条件的,这就要求Java虚拟机在启动时就要知道堆的大小,这样才能在堆的起始地址设置指针碰撞指针。因此,该方式不适用于动态扩展
堆内存的情况。
空闲列表 (内存不连续)
适用于动态扩展堆内存的情况。JVM维护一个空闲内存块列表,记录哪些内存块是可用的,而分配内存时,会遍历空闲列表,找到合适大小的内存块,并将其标记为已分配。这样,内存的分配可以更加灵活,不需要连续的内存空间。
缺点是会产生一定的内存碎片。该问题JVM采用了内存整理算法来解决,如压缩、分代回收等。
四、创建对象的5种方法
- 使用
new
关键字:可以调用类的构造方法来创建对象,并在堆内存中为对象分配空间。
Person person = new Person();
- 使用
反射
:通过Class
类的newInstance()
方法或者Constructor
类的newInstance()
方法可以创建对象。
Class<?> personClass = Class.forName("com.example.Person");
Person person= (Person) personClass.newInstance();
- 使用
clone()
方法:可以实现对象的浅拷贝或者深拷贝。
Person person = new Person();
Person clonedPerson= (Person) person.clone();
- 使用
对象工厂
:对象工厂用于封装对象的创建过程,可以根据需要返回不同类型的对象实例。
public class PersonFactory {
public static Person createPerson() {
return new Person();
}
}
Person person = PersonFactory.createPerson();
- 使用
静态工厂方法
:类中的静态方法可以作为工厂方法,用于创建对象实例。静态工厂方法通常有自定义的名称,便于描述对象的创建方式。
public class Person{
private Person() { }
public static Person createPerson() {
return new Person();
}
}
Person person = Person.createPerson();
五、浅拷贝与深拷贝
浅拷贝(Shallow Copy)
浅拷贝是复制对象本身和对象中的基本数据类型的字段,但不会复制对象中的引用类型字段。
在浅拷贝中,被复制对象和新创建的对象会共享引用类型字段
,这意味着如果改变了一个对象中的引用类型字段,那么另一个对象中的对应字段也会受到影响。
class Person {
private String name;
private Address address;
}
Person originalPerson = new Person("Patrick", new Address("BJ"));
Person copiedPerson = (Person) originalPerson.clone();
originalPerson
对象和copiedPerson
对象是两个独立的对象,但是它们共享同一个Address
对象,如果修改了copiedPerson
对象的Address
对象,那么originalPerson
对象的Address
对象也会随之改变。
浅拷贝常用的API有Spring的BeanUtils、Apache commons包中的PropertyUtils、实现Cloneable接口、Arrays的copyOf()方法
深拷贝(Deep Copy)
深拷贝是复制对象本身和对象中的所有字段,包括引用类型字段。不是复制该对象的地址,而是递归复制该对象所有引用类型成员的副本。即被复制对象和新创建的对象完全独立
,它们拥有各自的引用类型字段的副本,因此修改一个对象的引用类型字段不会影响另一个对象。
class Person {
private String name;
private Address address;
public Person deepCopy() {
Person newPerson = new Person(this.name, this.address.deepCopy());
return newPerson;
}
}
class Address {
private String city;
public Address deepCopy() {
return new Address(this.city);
}
}
Person originalPerson = new Person("Patrick", new Address("BJ"));
Person copiedPerson = originalPerson.deepCopy();
deepCopy()
方法分别在Person
和Address
类中实现了深拷贝。
深拷贝常用的5个API
- 重写clone方法,每个对象都要实现Cloneable接口并重写Object类中的clone方法
- 序列化时必须实现Serializable接口
- Apache commons工具包SerializationUtils.clone(T object)
- 通过JSON工具类实现深拷贝
- 通过手动
new
对象实现构造器方法的深拷贝
动态代理,本质上就是在特定的时机,去修改已有类型实现,或者创建新的类型。