程序计数器
- 什么是程序计数器?
线程的字节码文件(class)的行号指示器。 - 字节码执行原理
前端编译成字节码后,进入内存,并由执行引擎进行后端编译,编译方式有两种,动态编译和静态编译,动态编译就是对字节码逐行执行,静态编译是将热点代码编译为机器码并存储。在动态编译时逐行执行会需要用到程序计数器。 - 程序计数器的作用
使用指令一样可以做到循序执行,分支,循环等,为什么要加入程序计数器呢?
Java的多线程是通过时间片轮转实现。当一个线程的时间片结束,需要被挂起时,需要保存该线程执行字节码的行号。 - 程序计数器的特点
- 线程隔离
- 占用空间小
- 没有OutOfMemery
- 程序运行时,存储的是字节码地址
- 执行本地方法时,程序计数器为未定义(可以是任何值)
- 执行native方法完毕,如何回到正确的字节码位置呢?
https://blog.csdn.net/weixin_41884010/article/details/103593628
简单来说就是Java的线程会被映射到OS的线程,Java的程序计数器只是管Java某个线程的字节码执行到哪里了,而该线程映射到OS的线程会具有CPU的程序计数器、寄存器等等的资源,OS线程执行完native的c指令,会安全返回到Java的字节码编译的机器码中。
虚拟机栈
深入理解JVM-java虚拟机栈 - 李东平|一线码农 - 博客园
- 虚拟机栈是线程独立的,每个线程都具有一个虚拟机栈。
- 线程请求的栈的深度大于虚拟机允许的栈的深度,就会抛出StackOverflowException
- 当虚拟机栈可以动态扩容时,当内存耗尽,无法申请到新的内存空间时,会抛出OOM异常。
常用的扩容方式有SegmentStack和CopyStack,SegmentStack是当栈的容量满时,在申请一个新的栈,将两个栈相连(一般使用双向链表实现);CopyStack是当栈的容量满时,再申请一个更大的新栈,将旧的内容拷贝进新的栈。 - 虚拟机栈描述的时Java方法的内存模型,每个方法的执行与结束都代表着栈帧的入栈与出栈。
栈帧
- 栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构。
- 栈帧存储了方法的局部变量表、操作数栈、动态链接、方法返回地址等信息。
- 每个方法从调用开始到执行完毕的过程,对应着栈帧再虚拟机栈出栈和入栈的过程。
- 每个栈帧需要多大的局部变量表,多深的操作数栈在前端编译的时候就已经确定了。因此一个栈帧分配多大的内存不会因为程序运行改变,而与方法的局部变量和操作数的多少和虚拟机的实现有关。
在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个方法相关联的方法称为当前方法。
局部变量表
查看局部变量表:
javac -g:vars java源文件 javap -v 字节码文件
局部变量表用户存放方法参数及方法内部局部变量。
- 局部变量表在前端编译阶段就已经确定了该方法需要分配的局部变量和最大容量,容量以变量槽为最小单位。
- 局部变量在前端编译阶段就已经知道了各个变量的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用类型、和returnAddress类型。
- 如果是成员变量或者定义在方法外的对象的引用,他们存储在堆中。
变量槽
- 局部变量表的容量以变量槽为最小单位,一个变量槽占用32位内存空间,可以存储boolean、byte、char、short、int、float、reference。
- 对于64位长度的数据变量(double、long),虚拟机会以高位对齐的方式为其分配两个连续的slot。
对象引用类型
- 在Java堆中的存放的起始位置。
操作数栈
动态链接
一个方法中如果调用其他方法,需要将其他方法的符号引用转换为内存地址的直接引用,这些符号引用都存储在方法区中运行时常量池。
因此每个栈帧都包含一个指向运行时常量池的符号引用,以方便动态链接
方法出口
在方法退出后需要返回到方法被调用的位置,程序才能继续执行,方法出口保存上层方法的执行状态。具体的退出过程如下:
- 回复上层的局部变量表和操作数栈
- 把返回值(如果有)压入上层的操作数栈
- 调整上层PC计数器的值以只想方法调用指令后的下一条指令
- 出栈
本地方法栈
本地方法栈与虚拟机栈发挥的作用相似,只不过本地方法栈服务于虚拟机使用到的本地方法服务。堆空间
- 堆空间时虚拟机管理内存中最大的一块
- 堆是所有线程共享的
- 堆是存放对象实例和数组的
- GC回收对空间
- 为提升对象分配效率,线程共享的堆空间可以划分出多个线程私有的分配缓冲区
- 堆可以处于物理不连续空间,但逻辑上视为连续
- 堆空间可以实现为固定大小,也可实现为扩展大小(通过-Xmx和-Xms设定)
-
方法区
方法区是线程共享的内存区域
- 用于存储被虚拟机加载的类型信息,常量信息,静态变量、即时编译器编译后的代码缓存等数据
永久代 方法区不等同于永久代,仅仅是当时Hotspot虚拟机设计团队把收集器的分代设计扩展至方法区,这样使得Hotspot的GC能够像管理堆空间一样管理这部分内存。
当方法区无法满足新的内存分配需求时,将抛出OOM的错误。