PC
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
存储固定长度的内存地址,所以不会发生OOM
虚拟机栈
存储方法的调用情况,每调用一个方法就要压入这个栈帧,调用结束弹出。
局部变量表
- 存储方法的局部变量值和作用域。
- 存储this指针,方法参数,方法内部的局部变量。
- 当方法内部某个局部变量作用失效后,他所占据的slot可以被复用
- int类型占一个slot,long类型占两个slot
操作数栈
存放计算中间结果和操作数
动态链接
当要调用其他类的方法的时候,需要将符号引用转换成对应的运行时常量池中的内存地址。动态链接就保存了编号到运行时常量池的内存地址映射关系。
方法返回地址
- 方法返回后,PC怎么知道应该去哪里执行?这个就记录在栈帧里面。
- 如果函数调用陷入无限循环的话,就会导致栈中被压入太多栈帧而占用太多空间,导致栈空间过深。那么当线程请求栈的深度超过当前 Java
虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。 - Java 方法有两种返回方式,一种是 return 语句正常返回,一种是抛出异常。不管哪种返回方式,都会导致栈帧被弹出。也就是说,栈帧随着方法调用而创建,随着方法结束而销毁。无论方法正常完成还是异常完成都算作方法结束。
- 除了 StackOverFlowError错误之外,栈还可能会出现OutOfMemoryError错误,这是因为如果栈的内存大小可以动态扩展,如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
简单总结一下程序运行中栈可能会出现两种错误:
- StackOverFlowError: 若栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError 错误。
- OutOfMemoryError: 如果栈的内存大小可以动态扩展,如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
本地方法栈
虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务,存储native本地方法的栈帧。 和 Java 虚拟机栈共用一个栈空间。
堆
方法区
运行时常量池
存放类的基本信息,还有用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)的常量池表(Constant Pool Table) ,常量池表会在类加载后存放到方法区的运行时常量池中
字符串常量池
JDK7以前:运行时常量池包含字符串常量池一样被放在堆中永久代中
JDK7:字符串常量池被单独挪到了堆中,永久代中其他不变
JDK8:永久代被取消,取而代之的是直接内存的元空间,字符串常量池还在堆中。
JDK7以及之后的版本,intern方法字符串常量池只会存放
为什么取消永久代,放在直接内存的元空间?
主要是考虑到,永久代放在堆中很影响堆的垃圾回收效率,并且随着永久代越来越大,从未来角度来看,会很影响JVM的内存大小,如果放在直接内存中,方法区的内存就受本地系统影响。
为什么把字符串常量池单独挪到堆中?
字符串常量池中的常量经常会被回收,如果放在永久代,他被回收的概率很小,所以应该单独放在堆中,提高他的回收效率。