Java----初始化对象

这一篇主要分析一下当我们在程序里面new一个对象时发生了什么。

主要包括以下几个部分的内容:

  • 初始化对象的过程
  • 初始化对象的几种方式

1.简述java内存分区

要说清楚new对象的过程,那么就需要知道Java的主要内存分区:
以下主要来自下面的链接,没有了之前的幽默感:(非常有趣的公众号,跟了很久了)
码农翻身
先看看class文件的存放:

这里写图片描述

名称表示作用
程序计数器(Program Count Register)程序寄存器JVM支持多个线程同时运行,当每一个新线程被创建时,它都将得到它自己的PC寄存器(程序计数器)。如果线程正在执行的是一个Java方法(非native),那么PC寄存器的值将总是指向下一条将被执行的指令,如果方法是 native的,程序计数器寄存器的值不会被定义。 JVM的程序计数器寄存器的宽度足够保证可以持有一个返回地址或者native的指针。
栈(Stack)堆栈StackJVM为每个新创建的线程都分配一个栈。也就是说,对于一个Java程序来说,它的运行就是通过对栈的操作来完成的。栈以帧为单位保存线程的状态。JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作。我们知道,某个线程正在执行的方法称为此线程的当前方法。我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的Java堆栈里新压入一个帧,这个帧自然成为了当前帧。在此方法执行期间,这个帧将用来保存参数、局部变量、中间计算过程和其他数据。从Java的这种分配机制来看,堆栈又可以这样理解:栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
堆(Heap)Java堆(Java Heap)Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域。在此区域的唯一目的就是存放对象实例,几乎所有的对象实例都是在这里分配内存,但是这个对象的引用却是在栈(Stack)中分配。因此,执行String s = new String(“s”)时,需要从两个地方分配内存:在堆中为String对象分配内存,在栈中为引用(这个堆对象的内存地址,即指针)分配内存 .
方法区(MethodArea)MethodArea当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息,然后把这些类型信息(包括类信息、常量、静态变量等)放到方法区中,该内存区域被所有线程共享,如下图所示。本地方法区存在一块特殊的内存区域,叫常量池(Constant Pool),这块内存将与String类型的分析密切相关
本地方法栈(Native Method Stacks)本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

下面是class文件的加载流程:

  1. 首先存在一个class文件

  2. classloader初次找到这个class

  3. 然后会读取class的头文件,包括以下几种数据
    a. 0xCAFEBABE:判断是否为Java编译
    b. 50 , 0:判断版本号
  4. String, ArrayList分别有不同层次的loader加载,最顶层的叫Bootstrap Classloader , 下一次级叫Extension Classloader,最底层App Classloade
  5. 接着class会被加载到方法区
  6. 而堆中new出的该class类的对象来确认class是否被加载

这里写图片描述

8.而堆中new出的该class类的对象来确认class是否被加载
每个class会有局部变量区,还有一个操作数栈,类似这样,虚拟机所有的操作其实都是对于栈的操作
这里写图片描述
9. 然后取出结果,重新放入变量区
10. 而线程也不一定只有一个工作台,也可能有多个,但是只在最上面工作,这每个工作台叫栈帧,而多个工作台就是方法调用方法的结果
这里写图片描述

//上面大概将整体的流程描述了一遍,下面具体对new方法的流程进行解释

2.初始化对象的过程

“new”可以说是Java开发者最常用的关键字,我们使用new创建对象,使用new并通过类加载器来实例化任何我们需要的东西。

我们都知道,一个类为对象提供了蓝图,你从一个类创建一个对象。以下语句从createobjectdemo程序创建一个对象并将其赋值给一个引用变量:

Point originOne = new Point(23, 94);
Rectangle rectOne = new Rectangle(originOne, 100, 200);
Rectangle rectTwo = new Rectangle(50, 100);

第一行创建了一个 Point 类的对象,第二个和第三个线创建一个Rectangle 矩形类的对象。

这些陈述中的每一个都有三个部分(详细讨论):

  • 声明 Declaration:粗体代码是将变量名称与对象类型关联的变量声明。
  • 实例化 Instantiating :new关键字是一个java运算符,它用来创建对象。
  • 初始化 Initialization:new运算符,随后调用构造函数,初始化新创建的对象。
1.声明一个变量来指向一个对象,即引用

//首先我们要知道:
A a = new A();
其实可以看成两个部分

  • A a;
    声明一个变量用来指向一个对象(声明一个引用)
  • a = new A();
    调用构造方法,完成对象的初始化操作。

这才是创建一个对象的完整流程。
其中第二步完整流程为:

  1. 分配根据是否有参数分配内存空间,存储到堆,然后将新创建对象的引用至于栈顶
  2. 复制栈顶内容,此时栈顶有两个单元(复制的)都是新创建对象的引用
  3. 调用初始化方法,此时一个栈顶的引用作为此方法的参数消耗,完成对象的创建
  4. 将栈顶的对象的引用赋给局部变量
    (3,4分别消耗了一个引用,及新创建对象与此相连,局部变量与该引用相连)
    因为:
    基本数据类型作为局部变量是放在栈中的,new出来的对象是放在堆中的,用static声明的变量是静态变量,静态变量和字符串常量是放在data segment中的。
    特别注意构造方法的几个要点:

    • 构造方法没有返回值
    • 构造方法不是静态的
    • 父类的构造器调用子类的构造器有多态现象

在此之前,你知道,要声明一个变量,你需要写:

type name;

这将告诉编译器你将使用name引用一个type类型的对象。用一个原始变量,这个声明也保留了适当的内存量的变量。

你也可以在自己的行上声明一个引用变量。例如:

Point  originone;

如果你只是声明一个像originone这样的引用变量,其价值将待定,直到有一个对象真正被创造和分配给它。只是简单地声明一个引用变量而并没有创建一个对象。对于这样,你需要使用new运算符。在你的代码中使用它之前,你必须指定一个对象给originone。否则,你会得到一个编译器错误—–空指针异常。

2.实例化一个类对象

new运算符实例化一个类对象,通过给这个对象分配内存并返回一个指向该内存的引用。new运算符也调用了对象的构造函数。

注意:“实例化一个类的对象”的意思就是“创建对象”。创建对象时,你正在创造一个类的“实例”,因而“实例化”一个类的对象。

new运算符需要一个单一的,后缀参数,需要调用构造函数。构造函数的名称提供了需要实例化类的名称。

new运算符返回它所创建的对象的引用。此引用通常被分配给一个合适的类型的变量,如:
Point originone =new Point(23,94);

由new运算符返回的引用可以不需要被赋值给变量。它也可以直接使用在一个表达式中。例如:
int height = new Rectangle().height;

3.初始化一个类对象

这是Point类的代码

public class Point {
    public int x = 0;
    public int y = 0;
    //constructor
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}

这个类包含一个单一的构造函数。你可以识别一个构造函数,因为它的声明使用与类具有相同的名称,它没有返回类型。在Point类构造函数的参数是两个整数参数,如代码声明(int a,int b)。下面的语句提供了94和23作为这些参数的值:

Point originOne = new Point(23, 94); //结果可描述为下图

这里写图片描述

这是Rectangle类,包含4个版本的构造方法

public class Rectangle {
    public int width = 0;
    public int height = 0;
    public Point origin;
    // four constructors
    public Rectangle() {
       origin = new Point(0, 0);
   }
    public Rectangle(Point p) {
        origin = p;
    }
    public Rectangle(int w, int h) {
        origin = new Point(0, 0);
        width = w;
        height = h;
    }
    public Rectangle(Point p, int w, int h) {
        origin = p;
        width = w;
        height = h;
    }
    // a method for moving the rectangle
    public void move(int x, int y) {
        origin.x = x;
        origin.y = y;
    }
    // a method for computing the area of the rectangle
    public int getArea() {
        return width * height;
    }
}
    每个构造函数都允许你为矩形的起始值、宽度和高度提供初始值,同时使用原始类型和引用类型。如果一个类有多个构造函数,它们必须有不同的签名。java编译器区分构造函数基于参数的数量和类型。当java编译器遇到下面的代码,它知道在矩形类,需要一点争论,后面跟着两个整数参数调用构造函数:   

Rectangle rectOne = new Rectangle(originOne, 100, 200);

结果可描述为下图:
这里写图片描述

  1. Java关键字new是一个运算符。与+、-、*、/等运算符具有相同或类似的优先级。

  2. 创建一个Java对象需要三部:声明引用变量、实例化、初始化对象实例。

  3. 实例化:就是“创建一个Java对象”—–分配内存并返回指向该内存的引用。
  4. 初始化:就是调用构造方法,对类的实例数据赋初值。
  5. Java对象内存布局:包括对象头和实例数据。
    对象头:它主要包括对象自身的运行行元数据,比如哈希码、GC分代年龄、锁状态标志等;同时还包含一个类型指针,指向类元数据,表明该对象所属的类型。
    实例数据:它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)。
    在hotSpot虚拟机中,对象在内存中的布局可以分成对象头、实例数据、对齐填充三部分。
    对齐填充:它不是必要存在的,仅仅起着占位符的作用。

  6. Object obj = new Object();
    那“Object obj”这部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型数据出现。

    而“new Object()”这部分的语义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。

另外,在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。

总结

文章基于转载和自我理解。写完还是感觉不是很透彻。

1.在Java语法层面上创建一个对象,使用一个简单的new关键字即可,但是在JVM中细节的实现相当复杂,而且过程繁多。

2.当Java语法层面使用new关键字创建一个Java对象时,JVM首先会检查相对应的类是否已经成功经历加载、解析和初始化等步骤;当类完成装载步骤之后,就已经完全确定出创建对象实例时所需的内存空间大小,才能对其进行内存分配,以存储所生成的对象实例。

3.实例化之后,进行初始化(初始化对象头和实例数据)。

4.内存分配方式有:指针碰撞(Bump the Pointer)、快速分配策略、空闲列表(Free List)。

5.在并发环境下从堆中划分内存空间是非线程安全的,new运算符具有——-数据操作的原子性;也就是说创建一个Java对象分配内存,要么所有步骤都成功,返回对象的引用,要么回归到创建之前的内存状态,返回为NULL。

6.通过new创建一个Java对象,如果成功则返回这个对象的引用,开发者不可直接操作对象实例,需要通过这个引用“牵引”。

主要转载自:
http://blog.csdn.net/ljheee/article/details/52235915

参考:

码农翻身,非常感谢

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值