JVM对象的实例化
从字节码文件看创建对象的过程
- 判断对象对应的类是否加载、链接、初始化
虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。如果没有,那么在双亲委派模式下,使用当前类加载器已ClassLoader+包名+类名为Key进行查找对应的.class文件。如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class类对象。
- 为对象分配内存
首先计算对象占用空间大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小。
如果内存规整,使用指针碰撞分配内存空间。
如果内存不规整,虚拟机需要维护一个列表,执行空闲列表分配。
- 处理并发安全问题
采用CAS失败重试,区域加锁保证更新的原子性
每个线程预先分配一块TLAB
- 初始化分配到的空间
给对象的属性赋值操作
- 属性的默认初始化
- 显式初始化/代码块中初始化
- 构造器中初始化
- 设置对象的对象头
- 执行init方法进行初始化
对象的内存布局
- 对象头
- 实例数据
- 对齐填充
对象头
对象头包含两部分
- 运行时元数据(无锁,偏向锁,轻量级锁,重量级锁)
- 类型指针–指向最自己方法曲中的类元信息。
对象的访问定位
JVM如何通过栈帧中的对象引用访问到其内部的对象实例的呢。
对象访问方式主要有两种
- 句柄访问
- 直接指针
句柄访问
直接指针
直接内存
JDK8时引入了元空间
- 不是虚拟机运行时数据区的一部分。
- 直接内存时Java堆外的,直接向系统申请的内存区间
- 来源与NIO,通过存在堆中的DirectByBuffer操作Native内存
- 通常,访问直接内存的速度会优于Java堆(操作系统,内核态与用户态)
ByteBuffer byteBuffer - ByteBuffer.allocateDirect(BUFFER);
//直接分配本地内存空间
- 直接内存也会导致OOM异常
- 由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆带线啊哦,但是系统内存是有限的,Java堆和直接内存的综合依然首先与操作系统能给出的最大内存。可以通过
MaxDirectMemorySize
设置
- 由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆带线啊哦,但是系统内存是有限的,Java堆和直接内存的综合依然首先与操作系统能给出的最大内存。可以通过
在创建一个对象时java的静态块,静态成员变量,构造块,构造函数,与普通成员变量运行顺序
//父类Animal
public class Animal {
public static int a = 0;
public int b = 0;
static {
System.out.println("父类静态块1");
}
{
System.out.println("父类构造块1");
}
public Animal(){
System.out.println("父类构造函数1");
}
}
//子类Dog
public class Dog extends Animal {
int a=100;
static int b=112;
static{
System.out.println("子类静态块1");
}//1
static Dog st = new Dog();//2
Dog(){
System.out.println("子类构造函数1");
}
public static void staticFunction(){
System.out.println("staticFunction函数");
}
{
System.out.println("子类构造快1");
}
public static void main(String args[]){
staticFunction();
}
}
如上代码,若将1调至2之前,则输出为:
父类静态块1
子类静态块1
父类构造块1
父类构造函数1
子类构造快1
子类构造函数1
staticFunction函数
若将1调制2之后则输出为:
父类静态块1
父类构造块1
父类构造函数1
子类构造快1
子类构造函数1
子类静态块1
staticFunction函数
由此可见,static块和static成员变量的执行顺序是有字面顺序决定的。