运行时数据区域
Java运行时数据区域,按线程共享和线程隔离来分,可分为:
1.线程共享:方法区、Java堆、
2.线程隔离:虚拟机栈、本地方法栈、程序计数器
一.线程隔离的数据区域
1.程序计数器:可以看做是当前线程所执行的字节码的行号指示器,由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程,所以,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,使各个线程之间的计数器互不影响。(如果线程正在执行的是一个Java方法,则计数器:记录的是正在执行方法的虚拟机字节码指令的地址)
2.Java虚拟机栈:(生命周期与线程相同)描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储 ①.局部变量表 ②.操作数栈 ③.动态链接 ④.方法出口等信息。
①.局部变量表:存放了编译期可知的各种基本数据类型、对象引用和 returnAddress 类型。
3.本地方法栈:与虚拟机栈的作用非常类似,区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务(本地方法)0
二.线程共享的数据区域
1.Java堆:(是Java虚拟机所管理的内存中最大的一块)Java堆是所有线程共享的一块内存区域,在虚拟机启动的时候创建,此内存区域的唯一目的就是:存放对象实例以及数组。
Java堆是垃圾收集器管理的主要区域。Java堆中还可以分为:新生代和老年代。
2.方法区:同样也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法区中有一部分叫做运行时常量池,用于存放编译器生成的各种字面量和符号引用。
三.对象的创建
1.当虚拟机遇到一条new指令时,首先检查该指令的参数是否能定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须先执行相应的类加载过程。
2.检查完毕,虚拟机将为新生对象分配内存。对象所需内存的大小,在类加载完成后便可完全确定。分配内存有两种方式:指针碰撞和空闲列表。
3.内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。
4.接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。
5.上面的步骤,在虚拟机看来,一个新的对象已经产生了。但从Java程序的视角来看,对象得创建才刚刚开始---<init>方法没有开始执行,所有的字段都还为0,所以执行new指令后紧接着执行<init>方法。
四.对象的内存布局
对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
1.对象头包括两部信息:第一部分:用于存储对象自身的运行时数据 第二部分:类型指针,即对象指向他的类元数据指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
2.实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
3.对齐填充:并不是必然存在的,它仅仅起着占位符的作用,由于JVN自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是说对象的大小必须是8字节的整数倍,而对象头部分正好是8字节的倍数,因此当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
摘自《深入理解JAVA虚拟机》第二版。