1、运行时的数据区域
程序计数器(Program Counter Register)
线程私有
一块较小的内存空间,看作是当前线程所执行的字节码的行号指示器
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
若线程正在执行的是一个Java方法, 这个计数器记录的是正在执行的虚拟机字节码指令的地****址; 如果正在执行的是本地(Native) 方法, 这个计数器值则应为空(Undefined )
Java虚拟机栈 ( Java Virtual Machine Stack)
线程私有 生命周期与线程相同 为虚拟机执行Java方法(也就是字节码) 服务
每个方法(java方法)被执行的时候, Java虚拟机都会同步创建一个栈帧(Stack Frame) 用于存储局部变量表、 操作数栈、 动态连接、 方法出口等信息。
局部变量表存放了编译期可知的各种Java虚拟机基本数据类型
对这个内存区域规定了两类异常状况:
如果线程请求的栈深度大于虚拟机所允许的深度, 将抛出StackOverflowError异常;
如果Java虚拟机栈容量可以动态扩展, 当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常
本地方法栈
为虚拟机使用到的本地(Native)方法服务。
HotSpot虚拟机 直接将本地方法栈和虚拟机栈合二为一。
Java堆 (heap)
所有线程共享的一块内存区域, 唯一目的就是存放对象实例 ,Java堆是垃圾收集器管理的内存区域
Java堆既可以被实现成固定大小的, 也可以是可扩展的, 不过当前主流的Java虚拟机都是按照可扩
展来实现的(通过参数**-Xmx**和-Xms设定)
包含新生代和老年代,新建的的对象都从新生代分配内存,新生代由Eden Space和两个相同大小的Survivor Space构成。老年代用于存放新生代金国几次垃圾回收仍然存活的对象。
方法区 ( Mehtod Area)
所有线程共享的一块内存区域
用于存储已被虚拟机加载的类型信息、 常量、 静态变量、 即时编译器编译后的代码缓存等数据
运行时常量池 (Runtime Constant Pool)
运行时常量池是方法区的一部分
Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外, 还有一项信息是常量池表(Constant Pool Table) , 用于存放编译期生成的各种字面量与符号引用。
直接内存 (Direct Memory)
不是虚拟机运行时数据区的一部分, 也不是《Java虚拟机规范》 中定义的内存区域。
本机直接内存的分配不会受到Java堆大小的限制, 但是, 既然是内存, 则肯定还是会受到
本机总内存(包括物理内存、 SWAP分区或者分页文件) 大小以及处理器寻址空间的限
2、HotSpot构建对象
对象的创建过程
虚拟机完成以下工作:
①遇到一条new指令的字节码时,检查能否在常量池中定位到一个类的引用,否则先执行类加载
②虚拟机啊分配内存
③初始化内存空间为零值
④对对象进行必要设置。
java程序:调用构造函数,执行方法后,对象正在构造出来
对象内存布局
可以划分为三个部分: 对象头(Header) 、 实例数据(Instance Data) 和对齐填充(Padding) 。
对象头部分包括两类信息:
第一类是用于存储对象自身的运行时数据 (哈希码(HashCode) 、 GC分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳 );
第二类是类型指针, 即对象指向它的类型元数据的指针, Java虚拟机通过这个指针来确定该对象是哪个类的实例 。
实例数据:存储的有效信息
对齐填充:非必然存在,用来对齐补全
对象访问
①句柄访问 (间接访问)reference中存储的直接就是对象地址
②直接指针访问
HotSpot使用直接指针访问对象,其主要优势就是速度快
3、java源码编译机制
JVM规范中定义了class文件格式,但未定义java源码如何编译为class文件,各厂商在实现JDK时,通常会讲符合java语言规范的源码编译为class文件的编译器,如javac,其编译步骤如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m1erQ7uY-1646401411432)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210921111127461.png)]
分析和输入符号表:
Parse过程所作的为词法和语法的分析,词法分析要完成讲代码字符串变成token序列。语法分析要完成根据token生成抽象语法树
注解处理:
处理用户自定义的annotation,
语义分析和生成class文件:
进行一系列的语义分析