一、Java运行时数据区域
java运行时数据区域主要分为以下几个部分
1.程序计数器
程序计数器用来存储当前Java指令运行的位置,并指向下一条指令,占用的内存空间较小。对于一个处理器,Java多线程是通过划分时间片,在线程间切换来实现的,因此同一时间只有一个线程的指令在执行。所以每个线程都要有一个独立的程序计数器,保证切换到它时可以正确的执行下一条指令。如果执行的是java方法,这个计数器记录的是虚拟机字节码指令地址,如果是native方法,这个计数器是空值。
2.Java虚拟机栈
java虚拟机栈也是线程私有的,描述的是java方法执行的内存模型。每个方法执行都会创建一个栈帧,用于存储局部变量表,方法出口,操作数栈,动态链接等与该方法相关的信息。一个方法从开始到结束就对应一个栈帧在Java虚拟机栈中从入栈到出栈的过程。
3.本地方法栈
本地方法栈与Java虚拟机栈类似,不同的是本地方法栈为native方法服务。
4.Java堆
堆是线程共有的存储空间,存储一个应用中所有对象的实例,几乎所有的对象实例都在这里分配内存,在虚拟机启动时创建。根据对象 的年龄,分为新生代和老年代。由于是GC发生的主要区域,因此也叫GC堆。Java堆可以处于物理上不连续的内存空间。
5.方法区
方法区也是线程共有的存储空间,存储虚拟机加载的类信息,常量,静态变量等信息。常量池也是方法区的一部分,class文件中用到的字符串常量一般是到常量池的索引,以便节省存储空间。常量不仅仅在编译器进入常量池,程序运行期也可以将常量放进常量池中。
二、 对象
1.创建
Java面向对象,实例化一个对象在程序中通过new关键字实现,在JVM中主要分为以下几个步骤
(1)分配内存
分配内存在堆上实现,由于堆在物理存储上可以不连续,所以主要有两种内存分配机制:
1.如果堆是连续的,那么存在一个指针,其一侧为已经使用的空间,另一侧是未使用的,为对象分配内存就可以通过移动该指针实现。
2.如果堆不是连续的,JVM内部维护一个列表,记录了堆上哪些空间可用,哪些空间不可用,在分配内存时候找到足够该对象使用的内存标记,并更新列表。
这里还要考虑线程安全问题,要保证多个线程并发创建对象不出现错乱,这里主要有两种机制:
1.对分配内存空间的动作进行同步
2.对堆分区,每个线程对应一块区域,线程为对象分配内存时在自己的内存块中分配,自己内存块使用完再同步申请外部内存空间。
(2)内存区域初始化为零值
分配完区域之后要对拿到的内存区域初始化为零值,从而保证实例字段在不赋值的情况下也可以直接使用,只不过访问到的是该字段对应的零值。
(3)对对象进行必要的设置
接下来对对象头进行设置,比如该对象是哪个类的实例,对象的hashcode,对象的GC分代年龄等。
(4)对字段赋值
到这里才开始执行在构造器中指定的字段赋值。JVM执行new指令后执行 init 方法,把对象按照我们写的样子进行赋值,到这里一个对象才完整的产生出来。
2.布局
一个对象主要分为三部分:对象头, 实例数据,对象填充。
(1)对象头
hotspotVM中对象头分为两部分,一部分用于存储对象自身的运行时数据,比如hashCode,GC分代年龄,线程持有的锁等等。另一部分是类型指针,用以指定是那个对象的实例,如果是对象数组,还要指定数组的长度。
(2)实例数据
这部分区域存储的是该实例真正的有效信息,就是我们在代码中定义的各个字段,包括父类和对象本身的字段。
(3)对象填充
hotspotVM要求对象起始地址是8的整数倍,也就是对象大小必须是8的整数倍。所以如果大小不够的话需要填充补齐。