class文件描述的各种信息需要加载到虚拟机才能运行和使用,将class文件转移到虚拟机中你需要知道它是如何转移,又转移到哪里。此处说明转移到哪里
文章目录
引导
Java虚拟机将从系统得到的内存划分为不同区域来完成不同的用途,虚拟机采用自动内存管理机制使得我们不用为我们使用的class文件来分配空间,不同的数据区域有着不同的创建和销毁时间。
虚拟机的运行时数据区域
这是主要的class文件进入虚拟机要存放的地方,它有如下几个区域:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 方法区
- 堆
程序计数器 --线程私有
使用的空间较小,是字节码行号指示器来选区下一条指令。每个线程都有自己独立的程序计数器来保证多线程可以顺利的执行(切换到正确的执行位置)。唯一一个不涉及OOM情况的区域
虚拟机栈 --线程私有
描述的是Java方法执行的内存模型,也就是方法整个执行过程都在这里。每一个方法的内容总会有数据的操作,这一区域实现的就是把所有这个方法要使用的数据保存进来然后进行想要的处理得出结果的过程。每一个栈帧对应一个方法,每个方法从调用到执行完成的过程对应着一个栈帧在虚拟机栈中的入栈和出栈。那么栈帧是什么呢。。
栈帧 --一种数据结构
栈帧是用来支持方法的调用和方法的执行的一种数据结构,存放方法运行时所有的信息
**在编译程序代码的时候,栈帧需要多大的局部变量表,多深的操作栈都已经确定好了,在方法表的Code属性中,**因此一个栈帧需要多大的内存已经确定,不会受到运行期数据的影响。线程中的方法调用链很长,有多个方法处于执行状态(就是入栈了),但只有位于栈顶的栈帧才有效
栈帧包括这几个部分:
- 局部变量表
- 用来存储方法局部变量和方法参数的一组存储空间。
- 以变量槽作为单位(slot),64位用连续的两个slot
- 通过索引定位的方式来使用局部变量表,索引范围从0到最大容量数(Code属性中)
- 虚拟机是使用局部变量表来完成参数值到参数列表的传递
- 局部变量中的存储空间是可以被重用的(针对的是方法体中定义的变量)
- 局部变量的存储也是有顺序的,按照如下顺序来存放:
- 方法所属的对象的引用,也就是this 存放在索引0处
- 然后是方法表的参数表中的参数顺序
- 而后是方法体中定义的变量顺序和作用域来分配
- 除存储基本数据类型外,存储一个reference类型来表示对象的实例引用作用:
- 通过该引用可以直接或者间接的查找到该对象在堆中的数据存放的起始地址
- 直接或者间接的查找到对象对应的在方法区中的类型信息
- 局部变量定义后不赋初始值是不能使用的
- 操作数栈
- 最大深度在编译期写在方法表中的Code属性中
- 在方法执行过程中操作栈深度不会超过上面的最大深度
- 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,因为Java字节码指令是面向操作栈的,指令中会限制要操作的数据的类型。
- 针对操作栈的优化:两个栈帧出现部分重叠(下面栈帧的部分操作数栈与上面的部分局部变量表重叠),在方法调用的过程中公用一些数据来减少参数的复制。
- 动态连接
- 每个栈帧(方法,因为一个栈帧代表一个方法)指向运行时常量池的引用,也就是所属方法的引用。
- 动态连接是什么:class文件中的符号引用在每一次的运行期间转换为直接引用称为动态连接
- 方法返回地址
- 方法的两种退出方式
- 正常完成出口:遇到任意方法返回指令,返回给调用者。
- 异常完成出口:方法遇到的异常没有匹配到异常处理器(class文件中Code属性中的异常处理表和方法表中的Exceptions属性所罗列的异常),此种方式不会给上层调用者产生任何返回值。
- 方法的两种退出方式
- 虚拟机栈会抛出的异常:
- 线程请求栈深度大于虚拟机栈允许的最大深度抛出 StackOverflowError异常
- 虚拟机栈在动态扩展时无法申请到足够的内存就会抛出 OOMError异常
本地方法栈 --类似栈而服务不同对象
虚拟机栈为执行Java方法服务,本地方法栈为执行非Java方法(Native方法)服务。
抛出的异常也和虚拟机栈一般。
Java堆 --所有线程共享的一块区域,在虚拟机启动时创建
- new即会在堆上分配内存
- 唯一目的:存放对象实例。 虚拟机规范中“所有的对象实例和数组都要在堆上分配”。
- 垃圾收集器管理的主要区域。
- 堆会针对垃圾回收方式的不同来划分不同的区域,但堆存储的内容无影响(即它就为存储实例对象)。在垃圾收集时细说
- Java堆的OOM异常:堆无法为实例分配内存且堆无法扩展时。
- 分析:此时要确立堆中的对象是否有必要存活
-
内存泄露:躲过GC收集
-
内存溢出:也就是说内存中的对象都有活着的必要(垃圾回收时详细列举)
-
- 分析:此时要确立堆中的对象是否有必要存活
方法区 --线程共享
- 方法区存储的是已被虚拟机加载的:
- 类信息
- 常量
- 静态变量(类变量,属于类的变量)
- 编译后的代码(Code中的)
- 内存回收目标主要是针对常量池的回收和对类型的卸载。
- 运行时常量池:用于存放编译器生成的各种字面量和符号引用。
- 除了保存class文件中的符号引用外,还会把翻译出来的直接引用也存储在与运行时常量池中。
- 并非存在于class文件中常量池中的数据才能存储在这里
- 运行时常量池是方法区的一部分,无法申请到内存时抛出OOM异常。
虚拟机运行数据区之外的内存区域—直接内存
- 堆外内存,通过堆中存储的一个引用对象来间接操作该区域。
以上就是Java虚拟机使用的内存区域分布,不同的区域完成不同的任务,同时虚拟机就会知道在哪里寻找自己想要的数据,也可以根据不同区域村存储的数据的特点来及进行不同的管理。我们将编写的class文件从静态存储地转移到动态的内存中的存储布局,这些都是一一对应的。