一个java对象的产生
前言:当我们在代码中简单的通过 A a = new A(); 生成一个java对象的时候,中间发生了哪些事情呢?
这里主要进行了两个大的流程,先进行类的加载,然后进行对象的实例化
一.类的加载
这里在进行new A()时,jvm首先会查找类A是否已经加载到内存,如果之前没有加载过,这里就会进行加载类A到内存的流程。
所以类的加载在jvm生命周期中只会进行一次,那么在什么时候会进行类的加载动作呢?类的加载过程会在下面几种动作首次触发时进行
1.创建类的实例,也就是new一个对象
2.访问某个类或接口的静态变量,或者对该静态变量赋值
3.调用类的静态方法
4.反射(Class.forName("A"))
5.初始化一个类的子类(会首先初始化子类的父类)
6.JVM启动时标明的启动类,即文件名和类名相同的那个类
jvm在判断需要进行类加载后,会依次通过下面几个步骤进行类的加载
1.加载:这里会通过类加载器将A.class文件加载到jvm内存,将class文件中类的元信息转换为方法区中的元数据,并在堆中生成一个Class对象作为这些元数据的入口
2.验证:类加载器在加载class文件过程中,会通过一系列的检查校验class文件内容的有效性
3.准备:给类的静态变量赋予默认的零值
4.解析:jvm虚拟机将常量池内的符号引用替换为直接引用的过程
5.初始化:执行类构造器 <clinit>(),即初始化静态变量和执行静态代码块
这里在步骤1的加载过程中涉及到的类加载器是类加载过程的一个核心要点,需要进行特别说明
java的类加载器使用双亲委派模型(简而言之,各个类加载器之间建立逻辑上的父子关系,加载类时,先委托父类加载器进行类的加载,加载失败时自己再进行类的加载)进行类的加载。
jvm内置了3个重要的类加载器:
1.BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib
目录下的jar包和类或者或被 -Xbootclasspath
参数指定的路径中的所有类。
2.ExtensionClassLoader(扩展类加载器) :主要负责加载目录 %JRE_HOME%/lib/ext
目录下的jar包和类,或被 java.ext.dirs
系统变量所指定的路径下的jar包。
3.AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。
它们之间的关系如下:
注意这里的类加载器之间的父子关系不是通过类的继承实现的,而是通过类的组合方式实现的。
将 ClassLoader设计成委托模型的一个重要原因是出于安全考虑,例如如果编写了一个java.lang.String类并具有破坏性,不用委托模型的话,直接通过AppClassLoader在classpath下进行加载,就会覆盖jdk的String类,带来安全隐患。
补充说明:由于在类的加载过程中,线程使用了Class对象的锁,所以类加载过程是线程安全的。可以基于此实现一个线程安全的单例
二.对象的实例化
类加载成功(或已加载过)后,就开始进行对象的实例化了。对象的实例化依次进行了如下几个步骤:
1.对象在堆中的内存空间分配
2.初始化零值,这时会将实例变量都赋予零值
3.设置对象头,对象头保存了一些对象在运行期的状态信息,包括类信息地址(知道对象是属于哪个类的)、hashcode(用于hashmap和hashset等hash结构中)、GC分代年龄(可以依此确定对象何时该放入老年代)等
4.init方法执行,这时对变量的实例变量进行初始化
对象初始化的过程也是线程安全的动作