jvm 专业名词:运行时数据区(Runtime Data Area)、方法区(Method Area)、堆(Heap)、栈(Stack)、本地方法栈(Native)、程序计数器、执行引擎、本地方法接口(Java Native Interface 又称 JNI)。
程序计数器(PC寄存器)
在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任意时刻,一个CPU的内核只会执行一条线程中的指令,为了能够使得每个线程在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,程序计数器是每个线程是私有的。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出的现象(OutOfMemory)的。
Java栈
Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用、方法返回地址和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。
本地方法栈
本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是执行本地方法(Native Method)服务的。
堆
Java中的堆是用来存储对象本身的以及数组(数组引用是存放在Java栈中的)。堆是被所有线程共享的,在JVM中只有一个堆。
方法区
与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。
在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。
在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就会被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如 String 的 intern
方法。
在jdk1.7之后的版本,字符串常量池从方法区中转移到堆内存中。
运行时常量池和字符串常量池是不一样的。
在Java虚拟机规范中,对这个区域规定了两种异常情况:
1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出Stack Overflow Error 异常
2、如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
这两种情况存在着一些互相重叠的地方:当栈空间无法继续分配时,到底是内存大太小,还是已使用的栈空间太大,其本质上只是对同一件事情的两种描述而已。
方法区域,又被称为 “ 永久代 ”,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
1、为什么 JVM 堆内存不能超过 32 GB ?
压缩指针(Compressed Oops)
当堆内存超过 32 GB 时,JVM 自动禁用压缩指针,导致指针变为 64 位,内存占用增加,性能下降。
垃圾回收器的性能
大堆内存可能导致垃圾回收(GC)变得非常耗时,特别是对 Full GC 而言。较大的堆内存需要更多的时间来扫描和整理,从而导致更长的停顿时间。
内存碎片化
较大的堆内存更容易出现内存碎片化问题。内存碎片化会导致内存使用效率降低,并增加垃圾回收的复杂性和停顿时间。
JVM 内存管理的复杂性
较大的堆内存增加了内存管理的复杂性,JVM 在管理和分配内存时需要更多的资源和时间。