线程隔离区域
程序计数器
- 程序计数器(Program Counter Register Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
为什么是线程私有的?
- Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,也就是说,在同一时刻一个处理器内核只会执行一条线程,处理器切换线程时并不会记录上一个线程执行到哪个位置,所以为了线程切换后依然能恢复到原位,每条线程都需要有各自独立的程序计数器。
为什么是JVM规范中唯一没有规定OutOfMemoryError情况的区域?
- 它仅仅存储字节码文件的偏移地址,地址的大小是确定的
为什么正在执行的是Native 方法,则这个计数器值为空?
- Native方法大多是通过C实现并未编译成需要执行的字节码指令,也就不需要去存储字节码文件的行号了。
虚拟机栈
- 虚拟机栈是由一个个栈帧组成的,每一个方法被调用时后会产生一个栈帧。每一个栈帧都存储着局部变量表(基本数据类型[boolean,byte,char,short,int,float,long,double],对象引用),操作数栈(在方法的执行过程中,会有各种字节码往操作数栈里面写入和提取内容),每一个方法从调用到执行完成的过程中,都对应着栈帧从虚拟机栈中入栈到出栈的过程。
扩展:那么方法/函数如何调用?
- Java 栈可用类比数据结构中栈,Java栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入Java栈,每一个函数调用结束后,都会有一个栈帧被弹出。
Java方法有两种返回方式:
1.return 语句。
2.抛出异常。
不管哪种返回方式都会导致栈帧被弹出。
虚拟机栈可能出现内存溢出的问题吗?
一个线程中的方法调用链可能会很长,很多方法都同时处理执行状态。对于执行引擎来讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)。执行引用所运行的所有字节码指令都只针对当前栈帧进行操作。
- 线程执行过程中,方法递归调用,产生过多栈帧,会抛出StackOverflowError,栈帧的大小超过虚拟机所允许的限制,也会抛出StackOverflowError。
- 虚拟机栈允许动态扩展,当尝试扩展内存不足或者新的线程初始化新的虚拟机栈申请不到新的空间,就会报OutOfMemoryError
线程共享区域
java堆
- 所有对象的创建都在这个区域进行内存分配,所有的实例对象和数组都在这里分配对象。
方法区
- 它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。(jdk1.7也被称为永久代),jdk1.8的时候,方法区被彻底移除了(JDK1.7就已经开始了),取而代之是元空间,元空间使用的是直接内存。(只受本地内存大小的限制)
运行时常量池
- 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)。JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
直接内存
- 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。(这里涉及NIO),Java的NIO可以使用Native方法直接在java堆外分配内存,使用DirectByteBuffer对象作为这个堆外内存的引用。