- 1.Java运行时的内存区域

- a.程序计数器
程序计数器是一块较小的内存空间,是一个记录着当前线程所执行的字节码的行号指示器(指令的偏移地址)。
Java代码编译后生成字节码文件,通过“字节码解释器”进行解释执行,字节码解释器就是通过改变计数器的值来选取下一条执行的字节码指令。
简单理解:程序计数器保证了程序的正常执行。
程序的流程分为顺序执行、条件分支和循环三种。顺序执行:是指按照地址内容的顺序执行指令; 条件分支:根据条件执行任意地址的指令;循环:重复执行同一地址的指令。顺序执行的情况比较简单,每执行一个指令程序计数器的值就自动加1。但若程序中存在条件分支和循环,机器语言的指令就可以将程序计数器的值设定为任意地址(不是+1)。这样一来,程序便可以返回到上一个地址来重复执行同一个指令,或者跳转到任意地址。
举例:

程序运行的开始位置是0100地址。随着程序计数器数值的增加,当到达0102地址时,如果累加寄存器的值是正数,则执行跳转指令(jump指令)跳转到0104地址。也就是说,“跳转到0104地址”这个指令间接执行了“将程序计数器设定成0104地址”这个操作。
JVM的多线程是通过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的。某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。当被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置,在JVM中,通过程序计数器来记录某个线程的字节码执行位置。因此,程序计数器是具备线程隔离的特性,每个线程工作时都有属于自己的独立计数器。
举例:
当CPU执行权从 A 线程,转移到 B 线程的时候,JVM就要暂时挂起线程 A ,去执行线程 B ;当线程 A 再次得到CPU执行权的时候,又会挂起B线程,继续执行 A 线程 ;如果,我们只有一个程序计数器,切换B线程以后,程序计数器里面保存的就是B线程所执行的字节码的行号了,再切换回A线程,就不知道执行到哪里了,因为,程序计数器里面保存的是B线程当前执行的字节码地址 ;因此,要为每个线程都分配一个程序计数器,所以,程序计数器的内存空间是线程私有的 ;这样即使线程 A 被挂起,但是线程 A 里面的程序计数器,记住了A线程当前执行到的字节码的指令地址了 ,等再次切回到A线程的时候,根据程序计数器,就知道之前执行到哪里。
注意:线程的程序计数器,在线程被创建开始执行的时候,就要一同被创建;
特点:
- 线程私有的
- 是java虚拟机规范里面,唯一一个没有规定任何 OutOfMemoryError 情况的区域。
- 生命周期随着线程,线程启动而产生,线程结束而消亡。
-
如果执行的是java方法,那么记录的是正在执行的虚拟机字节码指令的地址的; 如果是native方法,计数器的值为空(undefined)。
稍作解释:栈和堆,都是可以通过运行时对内存需求进行扩增导致内存不够用的情况。但是,程序计算器仅仅只是一个运行指示器,它所需要存储的内容仅仅就是下一个需要待执行的命令的地址,无论代码有多少,最坏情况下死循环也不会让这块内存区域超限,因为程序计算器所维护的就是下一条待执行的命令的地址,所以不存在OutOfMemoryError。
一个Native Method是这样一个Java的方法:该方法的实现由非Java语言实现,一般是C++/C 写的,由系统调用,根本不会产生字节码文件,因此,程序计数器也就不会做任何记录 。
- b.虚拟机栈
虚拟机栈描述的是Java方法执行的动态内存模型:每个方法从执行都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从执行到结束,都对应一个栈帧的在虚拟机栈中的入栈和出栈。
特点:线程私有,并且生命周期和线程的线程周期保持一致。
Class Main{
public static void sleep(){
goBed();
Boolean isSleep = true;
if(isSleep){
System.out.println("Sleep....");
}
int number1 = 1;
int number2 = 2;
int number3 = number1 + number2;
}
public static void goBed(){
Boolean isGobed = true;
if(isGobed){
System.out.println("goBed....");
}
}
public static void main(String[] args){
sleep();
}
}
虚拟机栈遵循LIFO的思想,当main被调用时入栈,当main调用sleep时栈帧2入栈,依次类推;当goBed函数执行完,栈帧3出栈,依次类推。
每次的栈帧入栈对应着虚拟机栈的内存消耗,当栈的内存消耗完时,方法则不能进行调用这时便会出现stackOverFlowError,即内存溢出。
- 局部变量表
(由若干个Slot组成,单个Slot可以存储一个类型为boolean/byte/char/short/float/reference/returnAddress的数据,两个Slot可以存储一个类型为long或者double的数据)
存放编译期可知的各种基本数据类型、对象引用类型和returnAddress类型(指向一条字节码指令的地址:函数返回地址)。局部变量表所需的内存空间在编译期确定。
异常:线程请求的栈帧深度大于虚拟机所允许的深度—StackOverFlowError,如果虚拟机栈可以动态扩展(大部分虚拟机允许动态扩展,也可以设置固定大小的虚拟机栈),但是无法申请到足够的内存—OutOfMemorError。
- 操作数栈
JVM使用操作数栈作为运行的工作空间或者我们也可以说用来存储计算的中间结果。
每个机器的默认栈内存不大一样大概2m左右,可以通过定义数组的大小进行估算,当然也可以自己设置栈内存的大小。栈帧刚建立时,操作数栈为空,执行方法操作时,操作数栈用于存放JVM从局部变量表复制的常量或者变量,提供提取,及结果入栈,也用于存放调用方法需要的参数及接受方法返回的结果。
操作数栈可以存放一个JVM中定义的任意数据类型的值。在任意时刻,操作数栈都一个固定的栈深度,基本类型除了long、double占用两个深度,其它占用一个深度。
举例:把两个int类型的局部变量进行相减,并且将结果int值存储在第三个局部变量中

这里的iload_0和iload_1这两个指令会将值从局部变量数组中推入到操作数栈中。 isub指令会将这两个值相减然后把值存回到操作数栈中。然后在istore_2指令执行之后,结果值会从操作数栈中被弹出然后存入局部变量数组的第2个位置(索引为2)。

- c.本地方法栈
本地方法栈用于支持本地方法(Native方法,比如使用C/C++ 代码编写的方法)的执行,它和Java栈的作用类似。JVM中实现有本地方法栈时,和Java栈一样,允许被实现成固定或可动态扩展的内存大小,并且本地方法栈同样也会抛出StackOverflowError或者OutOfMemoryError异常。
疑问:为什么要使用Native Method?
Java使用起来非常方便,然而有些层次的任务用Java实现起来不容易(与一些底层系统如操作系统或某些硬件交换信息时的情况),或者我们想要更高的程序的效率,就需要与Java外的环境进行交互。这时候,本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且无需了解Java应用之外的繁琐的细节。
- d.Java堆
Java堆是java虚拟机所管理的内存中最大的一块,被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都是在这里分配内存。
Java堆是垃圾收集器管理的主要区域,从内存回收的角度看,现在收集器基本都在用分代收集算法,所以Java堆中还可以细分为:新生代和老年代;新生代又被划分为三个区域Eden、From Survivor, To Survivor等。
垃圾回收:https://www.cnblogs.com/smilexuezi/p/11568840.html
Java堆的大小是可扩展的, 通过-Xmx和-Xms控制。Java堆可以处于物理上不连续的内存中,只要逻辑上是连续的即可。如果堆内存不够分配实例对象, 并且对也无法在扩展时, 将会抛出outOfMemoryError异常。
- e.方法区
方法区域Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态方法、即时编译器编译后的代码等数据(也就是存储字节码文件.class)。

常量池,分为Class常量池和运行时常量池,运行时的常量池是属于方法区的一部分,而Class常量池是Class文件中的。
常量池存储的内容:

Class常量池:当Class文件被Java虚拟机加载进来后存在方法区的一些字面量和符号引用,字面量包括字符串,基本类型的常量,符号引用其实引用的就是常量池里面的字符串,但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引。
运行时常量池:在类加载后进入方法区的运行时常量池中,运行时常量池相对于class常量池一个重要的特征是动态性,Java并非要求常量一定只有编译期才能产生,即并不是先放入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,也就是String类的intern()方法。
结论: 静态常量池是针对每个被加载进入内存的class文件解析后,存放各个字面量值,符号引用的数据,而运行时常量区就是把所有的静态常量的数据汇总到一起,运行时常量池是class文件常量池在运行时的表示。

浙公网安备 33010602011771号