浅析对象的创建过程

个人博客

http://www.milovetingting.cn

浅析对象的创建过程

前言

我们平时在创建对象时,可能都会这样创建:

Object object = new Object();

看起来很简单的一个过程,那么这个new操作的背后,有哪些相关的知识点,是需要我们掌握的,本文针对这些来展开介绍。

对象的创建过程.png

对象的创建过程

类都是由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算法是标记清除算法。

posted @ 2020-05-21 16:14  milovetingting  阅读(344)  评论(0编辑  收藏  举报