Java对象的创建
学了很久的java,是时候来一波深入思考了。比如:对象是如何在JVM中创建,并且被使用的。本文主要讲解下new对象的创建过程。要想更深入的了解建议去认认真真的看几遍《深入理解Java虚拟机》周志明写的。
首先自己想想:Person p = new Person() 这句话是干嘛的?废话,肯定是创建一个新对象的,那么JVM是怎么帮你创建的呢?在哪创建的?怎么在堆上分配内存的?你又是如何去根据变量找到对应的对象的?带着这些问题,继续往下看:
一、找到能生成对象的Class类
对象又不是孙猴子,石头里蹦出来的。你new一个对象,总要找到能生成这个对象的类吧。所以JVM是这样做的:
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用(这些都会在包含你这个new对象的对象的常量池中记录),并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程,具体加载过程请看我下一篇博客。
二、堆上内存的分配
有能产生对象的类了,下面就是该在堆上创建你需要的对象了,首先,需要明确的是,对象的大小是已经确定好的,这是在类加载的时候就已经计算好的,包括方法在栈上栈帧的大小都是已经确定的,你只需要在堆上找到能存放这么大的一块区域就可以了。那么这个区域该怎么找呢?随便找一块能用的?还是说有其他的方法分配呢?
两种分配内存的方法:
- 指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边(这就是GC中的复制算法),中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)。
- 空闲列表:如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错(这就是GC中的标记-清理算法),那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录哪些内存块是可用的,有多大,在分配的时候从列表中找到一块足够大的空间划分给对象实例, 并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。
那么上述两种分配算法实际怎么用的呢?
选择哪种分配方式由 Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决 定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。
HotSpot采取G1垃圾回收器,其具有压缩整理功能,系统采用的分配算法是指针碰撞。
不知道你们发现没,这个两个内存分配方法好像只适用于单个线程,当有多个线程同时给多个对象分配内存,那该怎么办?毕竟多线程肯定会有竞争问题的存在。没毛病,下面讲解下两种解决多线程分配内存的方法:
- CAS同步处理:解决互斥的问题肯定是同步,而解决同步问题必然是加锁处理。所以,实际上虚拟机采用CAS(compareAndSet,乐观锁)配上失败重试的方式保证更新操作的原子性。
- 本地线程分配缓冲:把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。
三、对象的内存布局
房子(内存)有了,那如何把你的对象放进去呢?copy?肯定不是,你要放的有逻辑,条理,毕竟大家都是工科生,不能放的太随便,太艺术了。
首先,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头), 如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作解释了对象的实例字段在Java代码中为什么可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。(我个人认为:内存空间都初始化为零值,也是为了清空原有内存上的数据,来存放现有的数据这一作用)。
对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头:对象头包括两部分信息
- 第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
- 另外一部分是类型指针。虚拟机通过这个指针来确定这个对象是哪个类的实例,类型指针指向的是方法区对应的类。
实例数据:对象真正存储的有效信息
这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响。HotSpot虚拟机默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary ObjectPointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。主要包括成员变量,方法是保存在方法区里的。
对齐填充
由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
四、对象的初始化
上面都是JVM干的事,后台的,默认的,本节主要讲从代码初始化流程。
目前,对象已经基本创建完成,但是成员变量还是只是默认初始化,如何显示的初始化值,构造器,成员变量,代码块都是如何初始化的饿,顺序是什么?
举例说明:
/**父类*/
public class Father {
private static int b = 1; static {
System.out.println("父类静态初始化块");
} { System.out.println("父类初始化块"); } public Father() {
System.out.println("调用了父类无参构造器");
} public Father(int b) {
this.b = b;
System.out.println("调用父类的有参构造器");
}
} /**子类*/
public class Son extends Father {
private static int a =1;
static {
System.out.println("子类静态初始化块");
} { System.out.println("子类初始化块"); } public Son() {
System.out.println("调用子类的构造器");
}
public Son(int a){
this.a=a;
System.out.println("调用子类的有参构造器");
}
} public class Test {
public static void main(String[] args) {
Son son = new Son();
}
}
打印语句:
父类静态初始化块
子类静态初始化块
父类初始化块
调用了父类无参构造器
子类初始化块
调用子类的构造器
五、对象的访问定位
对象访问方式取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种。
- 句柄访问:
Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
- 直接指针方式:
reference中存储的直接就是对象地址
两种方式的区别:
- 使用句柄来访问的最大好处就是引用变量中存储的是稳定的句柄地址,对象被移动(在垃圾收集时移动对象是很普通的行为)时就会改变句柄中实力数据指针,但是引用变量所指向的地址不用改变。
- 而使用直接指针访问方式最大的好处就是速度更快,节省了一次指针定位的时间开销,但是在对象被移动时,又需要改变引用变量的地址。
虚拟机HotSpot,它就是使用的第二种方式进行对象访问的,但从整个软件开发范围来看,各种语言和框架使用句柄来访问的情况也十分常见。
此处有知识点:
类加载时 方法信息保存在一块称为方法区的内存中, 并不随你创建对象而随对象保存于堆中。如果instance method也随着instance增加而增加的话,那内存消耗也太大了,为了做到共用一小段内存,Java 是根据this关键字做到的,比如:instance1.instanceMethod(); instance2.instanceMethod(); 在传递给对象参数的时候,Java 编译器自动先加上了一个this参数,它表示传递的是这个对象引用,虽然他们两个对象共用一个方法,但是他们的方法中所产生的数据是私有的,这是因为参数被传进来变成call stack内的entry,而各个对象都有不同call stack,所以不会混淆。其实调用每个非static方法时,Java 编译器都会自动的先加上当前调用此方法对象的参数,有时候在一个方法调用另一个方法,这时可以不用在前面加上this的,因为要传递的对象参数就是当前执行这个方法的对象。
对象创建流程总结:
Person p = new Person()
1、由于是要创建Person类对象,java虚拟机(JVM)先去找Person.class文件,如果有的话,将其加载到内存。
2、没有的话进行类加载,将类型信息(包括静态变量,方法等)加载进方法区。
3、执行该类中static代码块。
4、到这时才进行堆内存空间的开辟,并为对象分配首地址。
5、在堆内存中建立对象的成员属性,并对其进行初始化(先进行默认初始化再进行显示初始化)。
6、进行构造代码块的初始化。
7、对象的构造函数进行初始化。
8、将堆内存中的地址(引用)赋给栈内存中的p变量。
参考文献:
Java对象的创建的更多相关文章
- 图解JAVA对象的创建过程
前面几篇博文分别介绍了JAVA的Class文件格式.JVM的类加载机制和JVM的内存模型,这里就索性把java对象的创建过程一并说完,这样java对象的整个创建过程就基本上说明白了(当然你要有基础才能 ...
- 深入理解Java对象的创建过程:类的初始化与实例化
摘要: 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类 ...
- (转)深入理解Java对象的创建过程
参考来源:http://blog.csdn.net/justloveyou_/article/details/72466416 摘要: 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一 ...
- Java对象的创建及使用
Java对象的创建及使用 对象是类的具体实例(instance),是真实存在的个体:
- Java对象的创建过程:类的初始化与实例化
一.Java对象创建时机 我们知道,一个对象在可以被使用之前必须要被正确地实例化.在Java代码中,有很多行为可以引起对象的创建,最为直观的一种就是使用new关键字来调用一个类的构造函数显式地创建对象 ...
- java 对象 :创建
灵感来自effective java 关于对象,是java的核心,如何有效的创建其实是一个值得关注的地方. 1.静态工厂:这是一个值得关注的,并且应该时刻考虑的方法. 优点:1.他是有名字的,这个是如 ...
- java --- 对象的创建过程
java 对象创建的过程 存在了继承关系之后,对象创建过程如下: 1.分配空间.要注意的是,分配空间不光是分配子类的空间,子类对象中包含的父类对象所需要的空间,一样在这一步统一分配.在分配的空间的时候 ...
- 2 Java对象的创建过程
JAVA中创建对象直接new创建一个对象,对么对象的创建过程是怎样的呢? 程序运行过程中有许多的对象被创建出来.那么对象是如何创建的呢? 一 对象创建的步骤 1 遇到new指令时,检查这个指令的参数是 ...
- java内存区域分析及java对象的创建
java虚拟机在执行java程序的过程中会将它管理的内存区域加分为若干个的不同的数据区域. 主要包括以下几个运行时数据区域,这里就只介绍经常会用到的 1:java虚拟机栈:我们常说的堆栈,栈就是指的j ...
随机推荐
- 开启属于你的GNOME桌面
图片剪辑源自美剧<黑客军团>(英语:Mr. Robot) GNOME(GNU Network ObjectEnvironment)是一种GNU网络对象模型环境 ,GNU计划的一部分,目的为 ...
- [COGS 2051] 王者之剑
Saber大法吼 2051. 王者之剑 ★★★☆ 输入文件:Excalibur.in 输出文件:Excalibur.out 简单对比时间限制:1 s 内存限制:256 MB [题目描述 ...
- ZOJ - 3469 Food Delivery (区间dp)
When we are focusing on solving problems, we usually prefer to stay in front of computers rather tha ...
- 独立ip的优势
独立ip的建站优势 我想很多人都想知道,那我就在这里给大家简单介绍下独立ip的优势有那些. 网站设计是需要很多专业知识的结合,整站不是那么容易就可以设计完成的 ...
- HPU--1280 Divisible
题目描述 给定一个很大的整数,我想知道它能否被9整除. 输入 有t组测试数据,每组数据给定一个整数N不存在前导0.(1 <= t <= 20,1 <= N <= 10^200) ...
- 【待整理】MySQL alter table modify vs alter table add产生state不一样
MySQL:5.6.35 OS:redhat5.8 今天更新数据库某些表字段,有如下两SQL: ①alter table xx modify xxxx;(表大概是77w) ②alter table s ...
- css 定位功能position
Static 定位 HTML元素的默认值,即没有定位,元素出现在正常的流中.静态定位的元素不会受到top, bottom, left, right影响. 相对定位Relative相对定位元素的定位是相 ...
- python编程基础知识—列表(一)
1 列表 用[]来表示列表,并用逗号分隔其中的元素.如: B=['trek','cannondale','redline','specialized'] print(B) ['trek', 'cann ...
- Linux - 简明Shell编程01 - 第一个脚本(HelloShell)
脚本地址 https://github.com/anliven/L-Shell/tree/master/Shell-Basics 示例脚本及注释 #!/bin/bash echo "hell ...
- HTML5 — Wed Storage简单示例
一.Wed Storage 概述 Wed Storage功能:在Wed上储存数据的功能,这里的储存是针对客户端本地而言的. 具体分为两种: sessionStorage,将数据保存在session对象 ...