本文整理自周志明老师的《深入理解Java虚拟机-JVM高级特性与最佳实践》第3版的第二章和第三章。
加上了一些网上拼拼凑凑的图片,个人认为很多博客复制来复制去,最后的东西都看不懂,所以从书里码了一下知识点,也用作自己记忆。
一、一个命令
上面的结果显示了 jvm 的模式:
Client VM(-client),为在客户端环境中减少启动时间而优化;
Server VM(-server),为在服务器环境中最大化程序执行速度而设计。
在文件路径:jdk-11.0.7+10\lib 下面可以更改 jvm.cfg 文件来决定是采用哪个模式,具体操作就是更改文件里面 Client 和 Server 这两行的位置,谁在上就是选择谁。
二、JVM 的内存区域与内存溢出异常
如上图所示,是 Java 虚拟机规范规定的,jvm 管理的内存区域。
- 灰色部分,即方法区和堆这两个数据区,是所有线程共享的数据区。
- 而白色部分,包括程序计数器、java虚拟机栈、本地方法栈,叫线程隔离的数据区,或者叫线程私有的内存。这三块内存区域随线程生,随线程死。
每个部分的详细介绍如下:
2.1 pc 寄存器( Program Counter)
也可叫程序计数器。是一块较小的内存空间,可以看作是当前线程执行的字节码的行号指示器。
在虚拟机的概念模型(注意只是概念)里,字节码解释器工作的时候就是通过改变这个计数器的值来选取吓一跳需要执行的字节码指令,显然,分支循环等基础功能都要靠这个计数器。
由于多线程实际上是线程轮流切换实现的,所以线程切换后为了能恢复到正确的执行位置,每个线程都要有一个独立的程序计数器。如果线程执行的是一个 java 方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地方法,计数器的值则为空(Undefined)。
此内存区域是唯一个在java虚拟机规范里没有规定任何 OutOfMemoryError情况的区域。
2.2 java 虚拟机栈
栈是方法执行的线程内存模型。每个方法执行的时候,jvm都会同步创建一个栈帧用于存储局部变量表、操作数栈等到,方法被调用直到执行完毕,就是对应一个栈帧在虚拟机栈里从入栈到出栈的过程。
大多情况栈主要指的是虚拟机栈里局部变量表的部分(实际上的划分要更复杂)。局部变量表存放了各种基本java数据类型、对象引用和 returnAddress 类型(指向了一条字节码指令的地址)。这些数据类型在局部变量表中以局部变量槽(Slot)来表示,其中64位长的long和double类型占用两个槽,其他的占一个。在编译期间,局部变量表的空间就会分配完成,方法运行期间不会改变局部变量表的大小。
java虚拟机规范对这个内存区域规定了两种异常:如果线程请求的栈深度大于虚拟机允许的深度,会抛出StackOverflowError;如果Java栈容量可以动态扩展,当扩展的时候无法申请到足够的内存会抛出OutOfMemoryError。
2.3 本地方法栈
本地方法栈和 java 虚拟机栈类似,区别只是虚拟机栈为虚拟机执行 Java 方法,本地方法栈是为虚拟机使用到的本地方法服务。
因此本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出抛出 StackOverflowError 和 OutOfMemoryError 异常。
2.4 java 堆
java 堆在虚拟机启动的时候建立,它是 java 程序最主要的内存工作区域。
java堆的唯一目的就是存放对象实例。在JVM所管理的内存中,堆区是最大的一块,堆区也是Java GC机制所管理的内存区域。需要注意,java堆只是逻辑上的连续区域,物理上可以不连续。
提到垃圾回收的时候总会说堆的区域划分,但是实际上java虚拟机规范没有规定,所谓的划分是各种虚拟机实现的风格决定的。这部分后面垃圾回收的时候还会讲。
java堆可以固定大小,也可以实现成可扩展,当前主流的虚拟机都是按照可扩展来实现,基于 -Xmx和-Xms参数来设定。
异常:如果堆内存不够,并且堆也无法扩展,抛出OutOfMemoryError。
2.5 方法区
用来存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
在java虚拟机里把他描述为堆的一个逻辑部分,但是又要和堆区分开,还有一个别名叫“非堆”。类加载子系统负责从文件系统或者网络中加载 Class 信息( ClassLoader 就是这个区域下的组件),加载的类信息就存放于方法区。(可以看到,这里保存的东西都是唯一份的东西)
关于垃圾回收的永久代&#x