什么是JVM?
- JVM它是Java Virtual Machine 的缩写,主要是通过在实际计算机模仿各种计算机功能来实现的。Java能够被称为“一次编译,到处运行”的原因就是Java屏蔽了很多的操作系统平台相关信息,使得Java只需要生成在JVM虚拟机运行的目标代码也就是所说的字节码,就可以在多种平台运行。
JVM的内存区域
了解JRE、JVM、JDK三者的关系是什么
JDK是Java程序员常用的开发包、目的就是用来编译和调试Java程序的。JRE是指Java运行环境,也就是我们的写好的程序必须在JRE才能够运行。JVM是Java虚拟机的缩写,是指负责将字节码解释成为特定的机器码进行运行,值得注意的是在运行过程中,Java源程序需要通过编译器编译为.class文件,否则JVM不认识。
JVM中各区域的作用
程序计数器
内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成
如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
JAVA虚拟机栈
线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
- 局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress 类型(指向了一条字节码指令的地址)
本地方法栈
和JAVA虚拟机栈作用几乎一致,区别在于JAVA虚拟机栈加载的是JAVA方法,而本地方法栈加载的是 Native方法服务,就是本地方法;
JAVA堆
对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。
方法区
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池
属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。
直接内存
非虚拟机运行时数据区的部分
JAVA虚拟机运行时数据区
JVM异常及其处理方式
- StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
- 引发 StackOverFlowError 的常见原因有以下几种:
无限递归循环调用(最常见)。
执行了大量方法,导致线程栈空间耗尽。
方法内声明了海量的局部变量。
native 代码有栈上分配的逻辑,并且要求的内存还不小,比如 java.net.SocketInputStream.read0
会在栈上要求分配一个 64KB 的缓存(64位 Linux)。
- 解决方法
修复引发无限递归调用的异常代码, 通过程序抛出的异常堆栈,找出不断重复的代码行,按图索骥,修复无限递归 Bug。
排查是否存在类之间的循环依赖。
排查是否存在在一个类中对当前类进行实例化,并作为该类的实例变量。
通过 JVM 启动参数 -Xss 增加线程栈内存空间,某些正常使用场景需要执行大量方法或包含大量局部变量,这时可以适当地提高线程栈空间限制,例如通过配置 -Xss2m 将线程栈空间调整为 2mb。
-
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。
-
解决方法
- 检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。
- 增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m