个人博客
浅析对象的创建过程
前言
我们平时在创建对象时,可能都会这样创建:
Object object = new Object();
看起来很简单的一个过程,那么这个new
操作的背后,有哪些相关的知识点,是需要我们掌握的,本文针对这些来展开介绍。
对象的创建过程
类都是由JVM
加载到内存中的,类加载采用双亲委派
机制,双亲委派机制具体信息,这里不作展开。类加载包含以下几个过程:
加载
加载Class信息到内存中,可以从Class文件读取,也可以从Zip
包读取,或者在运行时通过动态代理
读取。
连接
验证
验证Class文件的字节流
中包含的信息是否符合虚拟机要求。
准备
为类对象的成员变量分配内存并设置类的成员变量的初始值
。
解析
虚拟机将常量池中的符号引用
替换为直接引用
。
初始化
执行类构造器的init
方法。
使用
对象的使用过程。
卸载
当类的所有对象都释放后执行卸载。
创建方式
对象的创建方式可以直接通过new的方式来创建,但一般为便于实现对象全局唯一,都会通过单例为实现。
单例的实现简单来分,有以下几种:
-
饿汉式
-
懒汉式
-
静态内部类
-
枚举
饿汉式
private static Singleton instance = new Singleton();
private Singleton() {
}
public Singleton getInstance() {
return instance;
}
饿汉式存在的问题是不能按需加载。
懒汉式
线程不安全
private static Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
存在的问题:多线程的情况下,线程不安全。
线程安全
方法加锁
private static Singleton instance;
private Singleton() {
}
public synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
通过方法加锁,线程安全的问题是解决了,但是每次获取实例都会加锁,增加了开销。
代码加锁
直接加载
private static Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
锁不加在方法上,而是加在具体的代码块上。这种方式会存在创建两个对象的情况。
双重检查锁
- 双重检查锁-不加Volatile
private static Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
这种方式不会存在创建两个对象的情况。但会存在指令重排的问题。
- 双重检查锁-加Volatile
private static volatile Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
静态内部类
private Singleton() {
}
public Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
静态内部类的方式可以实现按需加载。
枚举
public enum Singleton {
INSTANCE;
public void doSomething() {
}
}
对象的内存结构
具体的可以参考上一篇文章。
对象头
Mark Word
包含HashCode,锁标识,对象年龄等信息。
Klass Pointer
对象的Class的内存地址
Length
这个只在数组类型的对象中存在,用来存放数组长度。
实例数据
存放对象的非静态成员变量信息。
对齐填充
JVM要求8字节对齐
对象分配
从GC的角度,可以将Java的堆分为新生代
和老年代
。新生代和老年代的大小比为:1:2。
新生代
用来存放新产生的对象。新生代又分为Eden、SurvivorFrom、SurvivorTo三个区域,比例为:8:1:1。新生代使用的GC算法是复制算法。
Eden区
新对象产生后保存的地方。如果是较大的对象,则直接进入老年代。
SurvivorFrom区
GC后保留下来的对象会保存在这个区域。
SurvivorTo区
SurvivorFrom在GC后保留下来的对象会进入这个区域。
老年代
新生代中的对象每经过一次GC后,对应的年龄就会+1,如果对象的年龄达到老年代的标准(默认15次),则会进入老年代。老年代使用的GC算法是标记清除算法。