对象创建过程
流程图如下所示:
类加载检查
检查class文件是否符合正常class信息,校验class文件中的标识信息,校验类是否争取
类加载过程
java 默认有三个类加载器,引导类加载器,ext类加载器,app类加载器
-
引导类加载器:c++维护的类加载器对象住,:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等。
-
ext类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包。
-
app类加载器:应用类加载器,默认加载classpath路径下的类包。
-
自定义类加载器(用户自己定义): 负责加载用户自定义路径下的类包
java默认使用双亲委派的机制进行加载类信息,即加载类的时候首先子加载器会委托给父加载器进行加载,如果父加载器没有类信息。
才会交给子加载器。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 检查当前类加载器是否已经加载了该类
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { //如果当前加载器父加载器不为空则委托父加载器加载该类
c = parent.loadClass(name, false);
} else { //如果当前加载器父加载器为空则委托引导类加载器加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non‐null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 ‐ t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) { //不会执行
resolveClass(c);
}
return c;
}
}
这种加载方式有以下优点:
1、沙箱安全机制:防止核心的类信息被篡改
2、避免类的重复加载:当父类已经加载过该类,子类不会重复加载
分配内存
在类完成加载严重之后,JVm虚拟机将为新生对象分配内存信息。在分配内存的时候有两个问题:
- 内存如何分配
- 并发情况下对象安全性
对于第一个问题来说,分配内存的时候要保证分配的内存不能与其他对象的内存进行交叉。
划分内存的方法:
- 指针碰撞:这种分配方式建立在内存是绝对规整的前提下,将堆内存区域分为两个部分,一部分是分配过内存的地方,一部分是未分配过内存的地方,使用指针指向的方式,将两块区域分别开来。分配内存时。
- 空闲链表:这个方式解决内存堆空间不连续的情况,已使用和未使用的空间在一起相互交融。在分配内存的时候,找到一块足够大的内存交给对象,但是这种方式会造成内存空间的碎片化
解决第二个问题有两种方法
- CAS 虚拟机采用CAS配上失败重试的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理。
- 本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存。通过-XX:+/- UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启-XX:+UseTLAB),-XX:TLABSize 指定TLAB大小。
初始化
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头), 如果使用TLAB,这一工作过程也 可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问 到这些字段的数据类型所对应的零值。
设置对象头
初始化零值之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对 象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。
执行方法
执行方法,即对象按照程序员的意愿进行初始化。对应到语言层面上讲,就是为属性赋值(注意,这与上面的赋 零值不同,这是由程序员赋的值),和执行构造方法。