以下一行代码内部发生了什么?
Person person = new Person();
根据JLS中的规定,Java对象的创建过程可以简要地描述为以下几个步骤:类加载、分配内存、初始化、调用构造方法、返回对象应用
.class
文件,并将其转换成可执行代码,使得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采用了内存整理算法来解决,如压缩、分代回收等。
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
new
对象实现构造器方法的深拷贝动态代理,本质上就是在特定的时机,去修改已有类型实现,或者创建新的类型。