Java虚拟机(JVM)在执行java程序的过程中会把它所管理的内存划分为若干个数据区域。
java运行时数据区域是如何划分的?分为线程共享和线程独享的两大区域
- 共享: 方法区和堆
- 独享: 虚拟机栈,本地方法栈,和程序计数器
1.Java堆:
线程共享,在虚拟机启动时创建。用于存放对象实例、数组。
java堆是垃圾收集器管理的主要区域,因此很多时候也被称为GC堆(Garbage Collected Heap)
可以处于物理不连续的内存空间中,逻辑上是连续的。
2.方法区:
线程共享,用于存储已被虚拟机加载的类信息(class二进制文件)、常量、静态变量、即时编译器编译后的代码等数据。也叫Non-Heap(非堆)
HotSpot使用永久代来实现方法区。
垃圾回收行为在该区域是很少出现的。该区域回收的目标主要是针对常量池的回收、对类型的卸载。
注意:方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待 !
jdk1.6字符串常量池在方法区中,1.7之后在堆中。运行时常量池一直在方法区中。
jdk1.8开始,移除永久代概念,方法区用元空间实现。
2.1运行时的常量池
是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等,还有常量池(Constant Pool Table),用于存放编译器生成的各种字面量和符号引用。会在类加载后存放到方法区的 运行时常量池中。
一般情况下,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。运行时常量池相对于Class文件常量池,有个很重要的特征----具备动态性。java并不要求常量一定只能在编译器产生,运行期间也可能有新的常量放入池中,比如String类的intern()方法。
3.程序计数器:
线程独享,保证切换后能恢复到正确的执行位置。每条线程都会有一个独立的程序计数器,各条线程之间互不影响,独立存储。
(概念模型,没有实现)是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。
java多线程是通过对线程的轮流切换并分配处理器的执行时间的方式来实现的,在任意时刻,一个处理器都只会执行某一个线程中的指令。
JVM规范中唯一没有规定OutOfMemoryError情况的区域。
如果正在执行的是Native 方法,则这个计数器值为空(undefined)。
4.java虚拟机栈:
线程私有。生命周期同线程。执行方法时,会同时创建一个栈帧(stack frame),存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法从被调用到执行完成的过程,对应着栈帧在虚拟机栈中入栈到出栈。(栈帧是方法运行期的基础数据结构)
栈内存的管理:通过压栈和弹栈操作来完成的,以栈帧为基本单位来管理程序的调用关系,每当有函数调用时,都会通过压栈方式创建新的栈帧,每当函数调用结束后都会通过弹栈的方式释放栈帧。
该内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
通常我们说的堆栈中的栈,就是虚拟机栈,或虚拟机中的局部变量表。存放了八大数据类型,引用类型(即reference类型),和returnAddress类型(指向了一条字节码指令的地址)。
其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。
会有两种异常:
1)请求的站深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
2)虚拟机动态扩展时,无法申请到足够的内存,会报OutOfMemoryError异常。
5.本地方法栈:
线程私有。类似虚拟机栈,前者是为jvm执行java方法(即字节码)服务,后者是为jvm的Native方法服务。