运行时数据区
一个jvm只有一个Runtime
每个线程独立的: 程序计数器 栈 本地方法栈
线程公用的:堆、方法区
程序计数器(pc寄存器)
pc寄存器用来储存指向下一条指令的地址,线程私有的,没有OOM
时间片 因为cpu在不断切换线程执行指令
虚拟机栈
虚拟机栈由栈帧组成,一个方法就是一个栈帧
栈是运行时单位,堆是存储单位
StackOverflow:线程请求分配的栈容量超过虚拟机栈的最大容量(Java虚拟机大小是固定的)
OutfMemoryError:虚拟机栈扩展内存时没有足够的内存去创建对应的虚拟机栈(Java虚拟机大小是动态的)
-Xss设置栈大小
栈帧组成
- 局部变量表(Local Table)
- 操作数栈(Operand Stack)
- 动态链接(Dynamic Likng)
- 方法返回信息(Method Return)
- 附加信息(Other Informations)
局部变量表
局部变量表也被称之为局部变量数据或本地变量表
定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用,以及returnAddress类型
由于局部变量表是建立在线程上的栈上,是线程私有数据,因此不存在数据安全问题。
局部变量表所需的容量大小再度编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中,方法运行期间是不会改变局部变量表的大小的。
slot
局部变量表最基本的储存单位是slot(变量槽)
参数值的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束。
在局部变量表中,32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(double、long)占用两个slot
- jvm会为局部变量表中的每一个slot分配一个索引
- 当一个实例方法被调用的时候,它的方法参数和方法内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个slot
- 如果变量占两个slot则jvm会分配两个索引,访问时访问初始索引(long和double类型会用两个索引)
- 如果当前帧由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot
- 栈帧中的slot是可以复用的,如果一个变量过了作用域,那么在其作用域之后申明的变量会复用之前已经过期的局部变量表的槽位
操作数栈(oeprand stack)
每一个独立的栈帧除了包含局部变量表之外还包含一个后进先出操作数栈
操作数栈,在方法执行过程中根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)
- 某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈。
- 比如执行 复制、交换、求和等操作
操作数栈主要保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的
每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在code属性中,为max_stack的值
栈中的任何一个元素都是可以任意的Java数据类型 - 32bit占用一个栈单位深度
- 64bit占用两个栈单位深度
操作数栈并非采用访问索引的方式来进行数据访问的,只能通过标准的入栈(push)和出栈(pop)操作来完成一次数据访问
bipush int型数据放入操作数栈
istore存到局部变量表
iload 把局部变量表加载到操作数栈
栈顶缓存技术
栈属于零地址指令,指令集小但是指令多,由于栈是存储在内存中的因此频繁的执行内存读/写操作必然会影响执行速度。为了解决这个问题hotspotjvm的设计者们提出了栈顶缓存技术,将栈顶元素全部缓存在物理cpu的寄存器中,以此降低对内存的读写次数,提升引擎执行效率。
动态链接(指向运行时常量池中的方法引用)
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。比如 invokenamic指令
在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存到class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
方法的调用指令
- invokestatic(非虚方法) 调用静态方法
- invokespecial(非虚方法) 调用int方法、私有以及父类方法
- invokevitual:调用虚方法(final也是使用该指令)
- invokeinterface:调用接口方法
动态调用指令 invokedynamic
方法返回地址
存放调用该方法的pc寄存器的值
一个方法的结束有两种方式: - 正常执行结束
- 出现未处理异常,非正常退出
无论哪一种方式退出,在方法结束之后都返回到该方法调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,而异常退出的返回地址时通过异常表来确定的,一般栈帧中不会保存这部分信息。
一些附加信息
栈帧中还允许携带与Jav虚拟机实现相关的一些附加信息。例如对程序调试提供支持的信息。
堆空间
一个JVM实例只存在一个堆内存,Java堆区在jvm启动的时候空间大小就确定了;所有线程共享Java堆,在这里还可以划分线程私有缓冲区(Thread Local Allocation Buffer,TLAB)。
所有的对象实例以及数组都应该在运行时分配在堆上,在方法结束的时候对象不会马上被移除,在垃圾回收的时候才会被移除
堆内存细分
Java 7之前:新生区、养老区、永久区
Java8:新生区、养老区、元空间
堆空间大小设置
-Xms用于设置堆区(新生区+养老区)的起始大小
- -X 是JVM运行参数
- ms memory start
-Xmx用于设置堆空间(新生区+养老区)最大内存大小
一旦堆区中的内存大小超过最大内存(Xmx)会抛出OutOfMemoryError
通常会将 -Xmx和-Xmx两个参数配置相同值,目的是能够在Java垃圾回收机制清理完堆区之后不需要重新分隔计算堆区,从而提高性能
年轻代、老年代
年轻代划分为 Eden、survior0和survior1(from和to)
配置新生代与老年代在堆中占比
默认:-XX:NewRatio=2 表示新生代占1,老年代占2(老年代比上新生代)
默认情况下 Eden和survior的比例是8:1:1
-XX:UseAdaptiveSizePolicy 关闭自适应内存分配
-XX:SurvivorRatio设置新生代中Eden和Survior的占比
几乎所有的Java对象都是在Eden中new出来的(除非对象大到超过Eden)
绝大部分的Java对象的销毁都是在新生代进行的
方法区
Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于传统语言的编译代码的存储区,或者类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用 的特殊方法(第 2.9 节)。
方法区是在虚拟机启动时创建的。尽管方法区在逻辑上是堆的一部分,但简单的实现可能会选择不进行垃圾收集或压缩它。本规范不要求方法区域的位置或用于管理已编译代码的策略。方法区域可以是固定大小,也可以根据计算需要扩大,如果不需要更大的方法区域,可以缩小。方法区的内存不需要是连续的。
Java 虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在方法区域大小可变的情况下,对最大和最小方法区域大小的控制。
以下异常情况与方法区相关:
- 如果方法区域中的内存无法满足分配请求,Java 虚拟机将抛出一个
OutOfMemoryError.
在jdk7以前习惯上把方法区称为永久代,jdk8开始使用元空间取代了永久代。
元空间不在虚拟机设置的内存中,而使用本地内存。
方法区用于存储已被虚拟机加载的l类型信息,运行时常量池,静态变量,即时编译器后的代码缓存。
类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储一下类型信息
- 这个类型的完整有效名称(全路径名)
- 这个类型的直接父类的完整有效名称
- 这个类型的修饰符(public,abstract,final)
- 这个类型直接接口的一个有序序列(实现多个接口是有循序的)
域(Field)信息
1.域相关信息
- 域名称
- 域类型
- 域修饰符(public,private,protected,static,final,volatile,transient)
2.域的声明顺序
方法(Method)信息
1.方法相关信息
- 方法名称
- 方法返回类型
- 方法参数的数量、类型、顺序
- 方法的修饰符
- 方法的字节码、操作数栈、局部变量表及大小(abstract和native除外)
- 异常表(abstract和native除外)每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获异常类的常量池索引
2.声明顺序
只有HotSpot才有永久代
jdk1.6及之前 有永久代,静态变量存放在永久代上
jdk1.7 有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中
jdk1.8及之后 无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆中
方法区的垃圾收集
方法区的垃圾回收主要回收两部分:常量池中废弃的常量和不再使用的类型