本文参考Jvm规范文档(https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html),描述的是一个抽象的JVM引擎相关内容。
一.概述
首先关于jvm内存结构,会有6个部分,这6部分根据这3点加以区分:必须实现还是选择实现;每个线程私有还是所有线程共享;出现异常StackOverflowError可能发生部分,异常OutOfMemoryError可能发生的部分。
二.JVM内存结构
根据规定虚拟机结构并不是虚拟机规范严格所限制的,实现基本读取class文件和一些操作外,不同执行引擎会不同,例如运行时数据区在内存的布局,垃圾回收器算法的使用,或者优化如何翻译成机器码。
对于运行时数据区域,也就是jvm内存,有6个结构,他们有的是在jvm生命周期而初始化或者销毁的,有的则是跟随线程的生命周期初始化或销毁。
1.程序计数寄存器( program counter Register),虚拟机是多线程的,每个线程都有自己的计数寄存器,线程的当前方法如果是不是本地(native)方法,计数寄存器记录的是jvm正在执行指令的地址;如果是本地方法,计数寄存器的值是未定义(undefined)
2.虚拟机栈(Java Virtual Machine Stacks),每个线程都有一个jvm栈(这是栈一段内存空间,不是数据结构的栈,和数据结构栈类似,fifo),保存在桢(frames)里;jvm栈在方法调用时创建,并保存了当前线程的本地变量和局部变量,保存jvm栈的桢可能是在堆(heap)上分配的,而且内存结构上不要去jvm栈是连续的。
jvm栈创建时可以是每个单独设置固定大小或者统一动态伸缩的方式,动态伸缩是通过初始化参数的最大值和最小值(maximun and minimum size)控制的。
有两个著名的错误就和jvm栈有关:StackOverflowError,由于线程需要的jvm栈大于能够提供的大小;OutOfMemoryError ,由于jvm栈尝试动态扩张的时候没有足够的内存空间,或者新线程要创建jvm栈但是内存空间不足;
3.堆(Heap),堆是被所有线程所共享的,它是jvm启动时候就初始化的运行时数据区域,所有类的实例和数组都是从这里分配的内存。堆存储的对象不会明确的释放,但是会被自动存储管理系统(垃圾回收gc)进行回收的,jvm规范没有要求gc清除特定的类型,具体选择由实现者去决定;堆可能也是固定大小或者动态扩缩容量的,动态伸缩控制也是参数堆最大最小值,内存结构上也是不要求连续性。
同样当计算需要堆的大小超过gc能提供的大小同样出现OutOfMemoryError
4.方法区(Method Area),方法区也是被所有线程所共享的,它是jvm启动时就初始化,用来保存了每个类的结构,例如运行时常量池,属性和方法的数据,特殊方法和构造器的代码。尽管方法区逻辑上是堆的一部分,但是jvm规范并没有要求有方法区,或者方法区如何管理编译的代码,所以实现方式中可能是被gc回收或压缩,也是同堆一样内存不要求连续,可通过参数调整,也会出现OutOfMemoryError
5.运行时常量池(Run-Time Constant Pool),运行时常量池保存了在class文件里属于常量池表(constant_pool table)的内容(常量池表,是为了方便jvm指令引用,包含17种常量,https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-4.html#jvms-4.4),运行时常量池提供的功能类似一般编程语言的符号表,但是它包含内容远多于符号表,既能存放编译时确定的数字字面值,也能保存运行时才确定的方法或者属性的引用对象。运行时常量池分配在方法区内,会在jvm加载类或者接口时候就被构造好。
当创建类或者接口时如果构造需要的空间不能被方法区满足也会出现OutOfMemoryError
6.本地方法栈(Native Method Stacks),jvm可能用到传统的栈,用来配合本地方法(不实用java写的方法),本地方法也可能会用到jvm的指令集。jvm的实现如果不要本地方法或者本地方法用的传统栈,那么本地方法栈就可以不用提供,但是要用的话就得在每个用到本地方法的线程分别分配本地方法栈。本地方法栈可以时固定或者动态伸缩的,如果是固定大小,每个本地方法栈就要单独分配大小。本地方法栈类似jvm栈,所以当计算中需要的本地方法栈大于可以提供的大小,出现StackOverflowError;本地方法栈扩张时候没有足够空间,或者新线程创建本地方法栈没有足够空间,出现OutOfMemoryError。
三.总结
按照规范理解来看:
是否存在角度,方法区,在方法区上的运行时常量池,还有本地方法栈是jvm规范没有强制要求有的结构,剩下的程序计数寄存器,jvm栈,堆是一定会有的;
是否线程私有角度,程序计数寄存器,jvm栈,本地方法栈是线程私有每个线程一个,堆,方法区,运行时常量池是所有线程共享;
异常表现角度,StackOverflowError 发生的地方有jvm栈和本地方法栈,它们在线程中所需大小超过允许大小,OutOfMemoryError 发生在jvm栈,本地放方法栈,堆,方法区,他们扩张或创建的过程没有足够空间;
最后本文内容是我的浅薄理解,如有任何误解地方感谢指教。