红色区域:多线程共享 垃圾回收基本都在堆空间(95%)完成,方法区(5%)一小部分
灰色区域:单独线程私有
一个jvm就是一个runtime实例,runtime对象相当于一个进程
进程是计算机分配内存的最小单位
jvm的线程
线程时一个程序里的运行单元,jvm允许一个应用有多个线程并行的执行
数据区
寄存器
每个线程都有自己的寄存器,生命周期跟随线程
寄存器用来存储下一条指令的地址,也就是即将要执行的代码。由执行引擎读取下一条指令
pc寄存器的两个常见问题
1.使用pc寄存器存储字节码指令地址有什么用?
因为cpu需要不停的切换各个线程,这个时候切换回来以后,就得知道接着从哪开始继续执行
2.pc寄存器为什么设置为线程私有?
为了能够准确记录各个线程正在执行的当前字节码指令地址,最好的办法就是为每一个线程都分配一个pc寄存器
虚拟机栈
tip:栈是运行时的单位,堆是存储的单位
概述:java虚拟机栈,早期也称java栈,每个线程创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应一次次的java方法调用,是线程私有的
一个线程对应一个java虚拟机栈
生命周期:生命周期和线程一致 随着线程的创建而创建 随着线程的消亡而消亡
作用:主管java程序的运行,它保存方法的局部变量,部分结果,并参与方法的调用和返回
局部变量(8种基本数据类型,引用对象的引用地址)
栈的优点:
- 栈是一种快速有效的分配内存方式,速度仅次于程序计数器
- 对于栈来说不存在垃圾回收方式(只有进栈弹栈的操作)
java虚拟机栈可以是动态的或者固定不变的
1.固定大小的虚拟机栈可能出现的问题:stackOverflowError异常
2.动态拓展可能出现的问题:outofMemoryError异常 没有足够的内存去扩容虚拟机栈
栈的存储单位
- 每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在
- 在这个线程上正在执行的每个方法都各自对应一个栈帧
- 栈帧也是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
栈的执行原理
-
不同的线程中所包含的栈帧是不允许存在相互调用的,即不可能在一个栈帧之中引用另外一个线程的栈帧
-
如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧
-
java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常,不管使用哪种方式,都会导致栈帧被弹出
栈帧的内部结构
每个栈帧存储着:
局部变量表 (局部变量数组或本地变量表)
定义一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型,对象引用以及返回值类型
-
值的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束
-
局部变量表,最基本的存储单位是slot(变量槽)、
-
局部变量表追踪存放编译器可知的各种基本数据类型(8种),引用类型,返回值类型
-
局部变量表里,32位以内的类型只占用一个slot,64位的类型占用两个slot
byte,short,char在存储之前被转换为int,boolean也被转换为int,0代表false,非0代表true
long和double则占据两个slot -
局部变量表是建立在线程的栈上的,是线程的私有数据,因此不存在数据的安全问题
-
局部变量表所需的容量大小是在编译期确定下来的,在方法运行期间是不会改变局部变量表的大小
-
-
-
静态方法是不允许使用this,super两个变量的,因为不存在于当前方法的局部变量表中
-
静态变量和局部变量的对比
- 变量的分类:
- 按数据类型来分:基本数据类型 引用数据类型
- 按在类中的位置来分:成员变量 局部变量
- 成员变量又可以分为两种: 类变量 实例变量
- 类变量在使用之前都经历过默认初始化赋值
- 实例对象:随着对象的创建,会在堆内存默认赋值
- 局部变量在使用之前必须进行显式赋值
- 变量的分类:
-
-
操作数栈
操作数栈里面保存的就是 Java 虚拟机要执行的指令
tip:栈可以用数组或链表实现
作用:操作数栈,在方法执行的过程中,根据字节码指令,在栈中写入数据或提取数据,即入栈/出栈
主要用于保持计算过程的中间结果,同时作为计算过程中变量临时的存储空间
虽然操作数栈是由数组来实现的,但是不能根据索引来访问值,只能入栈/弹栈
如果被调用的方法有返回值的话,其返回值会被压入当前的栈帧的操作数栈中,并更新pc寄存器中下一条需要执行的指令
-
动态链接(指向运行时常量池的方法引用)
指向运行时常量池的方法引用
tip:在字节码文件中,有专门一个区域存放常量叫做常量池
字节码文件放在方法区中,所以常量池也在方法区中 -
方法返回地址 方法正常退出或异常退出的定义
-
一些附加信息
-
本地方法栈
本地方法栈是线程私有的
可以设置本地方法栈的长度
本地方法栈是存放调用本地方法时的引用
在hotspot中,本地方法栈和虚拟机栈合二为一了
堆
tip:一个进程对应一个jvm实例 一个进程内部有多个线程
堆的核心描述:
- 一个jvm实例(一个进程)只有一个堆内存
- jvm堆区在jvm启动的时候即被创建,其空间大小也就确定了
- 堆内存的大小是可以调节的
- 堆可以在物理上不连续,逻辑上它应该被视为连续的
- 所有的线程共享java堆,在这里还可以划分线程私有的缓冲区
- 几乎所有的对象实例都在这里分配内存
- 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置
- 方法结束后,堆中的数据不会马上被移除,仅仅在垃圾回收的时候才会被移除
一旦执行new,就申请空间,并创建对象
堆的核心概述:堆空间细分:
- 新生代 伊甸园区 幸存者区 (from区,to区)
- 老年代
- 永久代
新生代对象分配与分配过程:
关于垃圾回收:频繁在新生区收集,很少在养老区收集,基本不在永久代(元空间的落地实现)收集
方法区
-
栈,堆,方法区的交互关系
-
方法区的理解
-
方法区与java堆一样,是各个线程共享的内存区域
-
方法区在jvm启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的
-
方法区的大小,跟堆空间一样,可以选择固定大小或者可以拓展
-
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多类,就会导致方法区溢出
-
关闭jvm就会释放这个区域的内存
-
-
方法区的内部结构
-
-
类型信息:
- 这个类型直接父类的完整有效名
- 这个类型的修饰符(public,abstract,final的某个子集)
- 这个类型实现接口的一个有序列表(多实现)
-
域信息:
- 域名称,域类型,域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
-
方法信息:
- 方法名称
- 方法的返回类型
- 方法参数的数量和类型(按顺序)
- 方法的修饰符
- 方法的字节码,操作数栈,局部变量表及下、大小
- 异常表
-
静态变量:
- 静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分
- 类变量被类的所有实例共享,即使没有类实例时也可以访问它
-
全局常量:static final
-
运行时常量池(方法区中的重要结构)
- 运行时常量池是方法区的一部分
- 常量池表是class文件的一部分,用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池
- 运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池
- jvm为每个加载的类型都维护了一个常量池,池中的数据项像数组项一样,是通过索引访问的
- 运行时常量池中包含多种不同的常量,包括编译器就已经明确的数值字面量,也包括到运行期解析后才能获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址
- 运行时常量池,相对于calss文件的常量池最大的区别就是具有动态性
-
在字节码文件中有一个常量池 (常量池是字节码文件的一部分):
-
常量池包括各种字面量和对类型,域和方法的符号引用
-
一个java源文件中的类,接口,编译之后产生一个字节码文件。而java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码中,换一种方式,可以存到常量池,这个字节码包含了指向常量池的引用(一般都为String类型),在动态链接的时候会用到运行时常量池
tip:字符串的拼接操作底层创建了一个StringBuilder,使用了append方法
-
几种在常量池内存储的数据类型包括:
- 数量值 - 字符串值 - 类引用 - 字段引用 - 方法引用
-
常量池可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等类型
-
本地方法接口
本地方法接口的作用是融合不同的编程语言为java所用 本地方法由native修饰