java创建对象的过程详解(从内存角度分析)

简介: java对象的创建操作其实我在《JVM系列之类的加载机制》一文曾经提到过,包含两个过程:类的初始化和实例化。

java对象的创建操作其实我在《JVM系列之类的加载机制》一文曾经提到过,包含两个过程:类的初始化和实例化。为此为了理解的深入,我们还需要再来看一下类的生命周期。一张图表示:

v2-a5e9c3ff9d5ba684204ae3fef6f61716_1440w.jpg

从上面我们可以看到,对象的创建其实包含了初始化和使用两个阶段。有了这个印象之后,我们就能开始今天的文章了。先给出这篇文章的大致脉络:

首先,介绍一下java中对象的创建基本知识然后,介绍一下对象初始化的顺序接下来,介绍一下创建对象的几种方式最后,进行一个总结。(从内存角度去分析:重点)重点


一、基本知识


我们知道,一个对象的创建过程包含两个过程:初始化和实例化


我们在使用一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。


实例化时候,java虚拟机就会为其分配内存来存放自己及其从父类继承过来的实例变量。在为这些实例变量分配内存的同时,这些实例变量先会被赋予默认值(零值)。在内存分配完成之后,Java虚拟机才会对新创建的对象赋予我们程序员给定的值。


小结:创建一个对象包含下面两个过程:


1、类构造器完成类初始化(分配内存、赋予默认值)

2、类实例化(赋予给定值)


二、类初始化


下面我们直接给出一个例子看一下java是如何初始化的。我们知道一个类中,往往包含静态变量、静态代码块、变量、普通方法、构造方法等信息。那么他们是如何初始化的呢?

public class InitialTest {
    //静态变量
    public static String staticField = "静态变量";
    //变量 
    public String field = "==普通变量==";
    // 静态代码块
    static {
        System.out.println( staticField );
        System.out.println( "静态初始化块" );
    }
    // 初始化块
    {
        System.out.println( field );
        System.out.println( "==初始化块==" );
    }
    // 构造方法
    public InitialOrderTest(){
        System.out.println( "构造器" );
    }
    public static void main( String[] args ){
        new InitialOrderTest();
    }
}

输出:

v2-af8b4fb6e57d9ea3d5038702bb23d08f_1440w.jpg

上面这个例子比较简单,我们再来看看带有父类和接口的情况。

第一步:定义一个父类

class FatherClass {
    // 静态变量 
    public static String parent_StaticField = "父----静态变量";
    // 变量
    public String parent_Field = "父类----普通变量";
    // 静态初始化块
    static {
        System.out.println(parent_StaticField);
        System.out.println("父类------静态初始化块");
    }
    // 初始化块 
    {
        System.out.println(parent_Field);
        System.out.println("父类-----初始化块");
    }
    // 构造器 
    public FatherClass() {
        System.out.println("父类--构造器");
    }
}

第二步:定义一个子类实现

public class SubSon extends FatherClass  {
    // 静态变量
    public static String son_StaticField = "子类--静态变量";
    // 变量 
    public String son_Field = "子类--变量";
    // 静态初始化块
    static {
        System.out.println(son_StaticField);
        System.out.println("子类--静态初始化块");
    }
    // 初始化块 
    {
        System.out.println(son_Field);
        System.out.println("子类--初始化块");
    }
    // 构造器
    public SubSon(){
        System.out.println( "子类--构造器" );
    }
    public static void main(String[] args) {
        System.out.println("子类-----main方法");
        new SubSon();
    }
}

第三步:看结果

v2-4fc37eb2f508f631fd3b752484604635_1440w.jpg

小结,类的初始化顺序,这样看确实不好记,不过没有继承关系的我们都能很好的看到。带继承关系的,使用网上一张图来表示

v2-7b8871318d9fdd74ff83c9e9530f2f6f_1440w.jpg

OK,类的初始化中的知识点基本上就是初始化的顺序。


三、创建对象的几种方式


其实对象的初始化就是在创建对象的时候由jvm完成的。对于创建对象,主要是研究创建对象的几种方式。下面一一的解答.这里给出6种方式,面试的时候足够你zhuangbility。


  1. 使用new关键字
  2. Class对象的newInstance()方法
  3. 构造函数对象的newInstance()方法
  4. 对象反序列化
  5. Object对象的clone()方法
  6. 使用Unsafe类创建对象
  7. 最后再揭晓。。。

OK,先认识一个,下面一个一个看。


(1)使用new关键字


Test t1 = new Test();
Test t2 = new Test("java的架构师技术栈");

(2)class的newInstance()方法

public static void main(String[] args) throws Exception {
    String className = "com.fdd.Test";
    Class clasz = Class.forName(className);
    Test t = (Test) clasz.newInstance();
}

首先我们通过Class.forName()动态的加载类的Class对象,

然后通过newInstance()方法获得Test类的对象


(3)构造函数的newInstance()方法

public static void main(String[] args) throws Exception {
   Constructor<Test> constructor;
   try {
        constructor = Test.class.getConstructor();
       Test t = constructor.newInstance();
   } catch (Exception){
        e.printStackTrace();
   }
}

类Constructor也有newInstance方法,这一点和Class有点像。从它的名字可以看出它与Class的不同,Class是通过类来创建对象,而Constructor则是通过构造器。


(4)序列化

public static void main(String[] args) throws Exception {
     String filePath = "sample.txt";//序列化的路径
     Test t1 = new Test("java的架构师技术栈");
     try {
        //t1开始序列化
        FileOutputStream fileOutputStream = new FileOutputStream(filePath);
        ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
        outputStream.writeObject(t1);
        outputStream.flush();
        outputStream.close();
        //t2开始反序列化
        FileInputStream fileInputStream = new FileInputStream(filePath);
        ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
        Test t2 = (Test) inputStream.readObject();
        inputStream.close();
        System.out.println(t2.getName());
     } catch (Exception ee) {
        ee.printStackTrace();
     }
}

首先我们要对Test实现Serializable接口。然后开始序列化数据。最后得到反序列化的对象。


(5)clone方式


Object对象中存在clone方法,它的作用是创建一个对象的副本。

public static void main(String[] args) throws Exception {
    Test t1 = new Test("java的架构师技术栈");
    Test t2 = (Test) t1.clone();
    System.out.println(t2.getName());
}

(6)使用Unsafe类创建对象


Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类,如果真是如此影响就太大了。

我们无法直接创建Unsafe对象。这里我们使用反射方法得到

private static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
            return unsafe;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
  }

拿到这个对象后,调用其中的native方法allocateInstance 创建一个对象实例

Object event = unsafe.allocateInstance(Test.class);


四、总结


我们想要创建一个对象。基本上就是java虚拟机分配内存的过程。因此我们可以先回顾一下java程序的执行过程。给一张网上的图,写的很清晰

v2-143bbbdc6b100e1d8b712783574bcb10_1440w.jpg


一个例子去解释:(摘自我之前的文章《java8的内存结构》)

public class Person{
    int age;
    String name;
    public void walk() {
        System.out.println("我正在走路。。。。");
    }
}

然后我们测试一下:

public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "java的架构师技术栈";
        person.age = 18;
        person.walk();
    }
}

我们分析一下这个过程


第一步,JVM去方法区寻找Test类的代码信息,如果有直接调用,没有的话使用类的加载机制把类加载进来。同时把静态变量、静态方法、常量加载进来。这里加载的是(“冯冬冬的IT技术栈”,“冯XX”);这是因为字符串是常量,age中的18是基本类型。


第二步,jvm进入main方法,看到Person person=new Person()。首先分析Person这个类,同样的寻找Person类的代码信息,有就加载,没有的话类加载机制加载进来。同时也加载静态变量、静态方法、常量(“我正在走路。。。”)


第三步,jvm接下来看到了person,person在main方法内部,因而是局部变量,存放在栈空间中。


第四步,jvm接下来看到了new Person()。new出的对象(实例),存放在堆空间中。


第五步,jvm接下来看到了“=”,把new Person的地址告诉person变量,person通过四字节的地址(十六进制),引用该实例。 是不是有点晕,别着急,画个图看一下。

v2-03499693ec42e4f0afc936d84a804f71_1440w.jpg

第六步,jvm看到person.name = “冯冬冬的IT技术栈”;person通过引用new Person实例的name属性,该name属性通过地址指向常量池的"冯冬冬的IT技术栈"。


第七步,jvm看到person.age = 18; person的age属性是基本数据类型,直接赋值。


第八步,jvm看到person.walk(); 调用实例的方法时,并不会在实例对象中生成一个新的方法,而是通过地址指向方法区中类信息的方法。走到这一步再看看图怎么变化的

v2-88ee44c059183129097989194444b021_1440w.jpg

大多数人基本上都能看懂。创建一个对象的过程基本上就是这。

如有问题还请批评指正。谢谢

相关文章
|
14天前
|
存储 Java 编译器
Java内存区域详解
Java内存区域详解
29 0
Java内存区域详解
|
16天前
|
设计模式 Java
Java中创建对象的方式
Java中创建对象的方式
23 5
|
24天前
|
缓存 算法 Java
Java内存管理与调优:释放应用潜能的关键
【4月更文挑战第2天】Java内存管理关乎性能与稳定性。理解JVM内存结构,如堆和栈,是优化基础。内存泄漏是常见问题,需谨慎管理对象生命周期,并使用工具如VisualVM检测。有效字符串处理、选择合适数据结构和算法能提升效率。垃圾回收自动回收内存,但策略调整影响性能,如选择不同类型的垃圾回收器。其他优化包括调整堆大小、使用对象池和缓存。掌握这些技巧,开发者能优化应用,提升系统性能。
|
16天前
|
Java 调度
Java中常见锁的分类及概念分析
Java中常见锁的分类及概念分析
16 0
|
16天前
|
Java
Java中ReentrantLock中tryLock()方法加锁分析
Java中ReentrantLock中tryLock()方法加锁分析
13 0
|
20天前
|
缓存 安全 Java
Java并发编程进阶:深入理解Java内存模型
【4月更文挑战第6天】Java内存模型(JMM)是多线程编程的关键,定义了线程间共享变量读写的规则,确保数据一致性和可见性。主要包括原子性、可见性和有序性三大特性。Happens-Before原则规定操作顺序,内存屏障和锁则保障这些原则的实施。理解JMM和相关机制对于编写线程安全、高性能的Java并发程序至关重要。
|
2天前
|
设计模式 JavaScript Java
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
[设计模式Java实现附plantuml源码~行为型] 对象状态及其转换——状态模式
|
2天前
|
Java 程序员 数据库连接
Java从入门到精通:3.3.2性能优化与调优——内存管理篇
Java从入门到精通:3.3.2性能优化与调优——内存管理篇
Java从入门到精通:3.3.2性能优化与调优——内存管理篇
|
3天前
|
存储 安全 Java
滚雪球学Java(19):JavaSE中的内存管理:你所不知道的秘密
【4月更文挑战第8天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
30 4
滚雪球学Java(19):JavaSE中的内存管理:你所不知道的秘密
|
4天前
|
Java
Java基础之对象的引用
Java基础之对象的引用
5 0