(个人理解有不同看法可以讨论下哦)
对象创建出来可以分为两步
1、类加载
2、对象创建
1、类加载
类加载的时候 分五步 1、加载 -> 2、验证 -> 3、准备 -> 4、解析 -> 5、初始化
1、加载
通过io从磁盘上找到字节码文件,类使用的时候才会加载,比如调用类main方法时,
在加载阶段会在内存中生成一个代表这个类的class对象,作为方法区这类的各种数据的入口
其中类加载的时候是jvm虚拟机加载launcher,然后launcher再加载extclassload和appclassload加载器
类加载器有四种
- 引导类加载器(负责加载支撑jvm运行的位于jre的lib目录下的核心类库,比如rt.jar,charsets.jar)
- 扩展类加载器:加载jre的lib目录下的ext扩展目录中
- 应用程序类加载器:加载classpath路径下的包,加载开发者写的类
- 自定义加载器:加载自己指定位置的类
其中类加载的时候有一个概念双亲委派机制,就是比如应用程序类加载器加载一个类要先委托给父类加载,父类加载不到再让子类加载
用双亲委派机制有两个好处:
1、沙箱安全机制,防止核心类库被修改
2、避免类重复加载,如果父类加载了子类就不用加载了
tomcat为什么打破双亲委派机制呢
- 一个web容器可能需要部署两个系统,但是两个系统依赖的jar包版本不同
- web容器支持jsp文件修改热部署
注意:同一个JVM内两个相同包名和类名对象可以共存,因为类加载器可以不一样,只有类加载也一样的时候才能认为是同一个类
2、验证
验证字节码文件的正确性
3、准备(分配内存)
为静态变量分配内存,并赋予默认值,到这一步类已经加载完成,下面就是给对象分配内存了
4、解析
将符号引用替换为直接引用,比如将main方法替换为指向数据在内存中的指针或者句柄
这个就是静态链接的过程(在类加载阶段完成)
动态链接在程序运行期间将符号引用替换成直接引用
5、初始化
对类的静态变量初始化,执行静态代码块
2、对象创建
对象创建流程呢是 1、类检查(如果类没有加载先加载类) -> 2、分配内存 -> 3、初始化 -> 4、设置对象头 -> 5、执行init方法
1、类检查
虚拟机执行遇到new 对象 的时候先去检查这个指令的参数在常量池中有没有符号引用,并且检查这个符号引用代表的类是否已经加载,解析,和初始化过
new指令对应到语言的层面上时,new关键字、对象克隆、对象序列化
2、分配内存
类检查过后就要给对象分配内存了,对象需要多大空间类加载的时候就确定了,分配内存呢是从java堆中划分出来
这个步骤分配内存的时候有两个问题
1. 如何分配内存
划分内存的方法:
- 指针碰撞(bump the pointer)(默认是指针碰撞):如果java堆中内存是绝对的规整,用过的和没用过的有一个指针分界线
- 空闲列表(free list):如果java堆中的内存不是规整的,已使用和未使用的交错在一起,虚拟机就得维护一个列表来记录那些内存是可用的
2. 并发情况下,内存怎么分配
- cas(compare and swap):循环抢内存
- 本地线程分配缓冲(Thread Local Allocation Buffer,tlab):给每个线程预先分配一块内存做缓冲
通过XX:+/
UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启XX:+UseTLAB),XX:TLABSize 指定TLAB大小。
3、初始化
内存分配完成后,虚拟机需要将除了对象头空间内存空间都初始化为0,如果是用tlab这个内存空间初始化为0可以提前到分配tlab的时候进行。所以java对象的实例字段 不用初始化就可以使用,c++中实例要先给初始值
4、设置对象头
初始化之后,就要设置对象头的必要设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄
在HotSpot虚拟机中,对象存储分三块
1、对象头(Header)
对象头有两部分
1、存储对象自身的运行时数据:哈希吗、GC分代年龄、锁状态的标志、线程持有的锁、偏向线程id、偏向时间戳
2、类型指针:对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个实例
这个部分有一个点GC的分代年龄最大是15 因为分代年龄的空间是4bit
2、实例数据(Instance Data)
3、对齐填充(Padding)
5、执行init方法
从语言层面上来说就是为属性赋值,这个赋值是程序员给的值,和构造方法