JVM,Java虚拟机,是Java实现跨平台运行的核心组件,也是面试必考的知识点,本篇博文全篇默认标记为重要。
Java程序执行流程
JVM知识结构
Java内存结构(内存模型)是其他知识点的基础。
介绍下Java内存区域(运行时数据区) / 介绍下JVM内存模型
Java虚拟机在执行Java程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK1.8和之前的版本有所不同。
JDK1.8之前:
JDK1.8之后:(即原方法区从运行时数据区移到直接内存中的元空间)。
线程共享的:
- 堆
- 方法区
- 直接内存(非运行时数据区的一部分)
线程私有的:
- 程序计数器
- 虚拟机栈
- 本地方法栈
堆
堆区是java虚拟机所管理内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域唯一的目的就是存放对象实例,几乎所有对象实例以及数组都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点:Eden区、From Survivor区、To Survivor区、Tentired区等。进一步划分的目的是更好地回收内存或者更快地分配内存。
方法区
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。尽管Java虚拟机规范将方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-heap,目的是与Java堆区区分开。
程序计数器
程序计数器是一块较小的内存空间可以看作当前线程所执行的字节码的行号指示器。其主要功能概括为如下:
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如顺序执行、选择、循环、异常处理。
- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候知道该线程上次运行到哪里。
程序计数器是唯一一个不会出现OOM
的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
Java虚拟机栈
同程序计数器,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是Java方法执行的内存模型,每次调用的方法都是通过栈传递的。
Java内存可以粗糙的区分为堆(Heap)内存和栈(Stack)内存,其中栈就是现在说的虚拟机栈,或者说虚拟机栈中的局部变量表。(Java虚拟机栈由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口等信息。)
操作数栈的作用
Java虚拟机栈出抛出两种异常:StackOverflowError
和OutOfMemoryError
。
StackOverFlowError
:若Java虚拟机的内存大小不允许动态扩展,那当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就会抛出StackOverFlow
。OutOfMemoryError
:若Java虚拟机的内存大小允许动态扩展,且线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
什么情况下会发生OOM?
内存泄露:申请的内存在被使用完后没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请该内存的对象不再使用该内存,而该段内存又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
每一次方法调用都会有一个对应的栈帧被压入虚拟机栈,每一次方法调用结束(return或者抛出异常),都会有一个栈帧被弹出。
编写会发生StackOverFlow和OutOfMemory的程序
StackOverFlow
典型即为递归方法没有递归出口,无限制递归下去。
代码语言:javascript复制class Test {
private static int fibonacci(int n) {
// if(n == 0) return 1;
// if(n == 1) return 1;
return fibonacci(n-1) fibonacci(n-2);
}
public static void main(String[] args) {
System.out.println(fibonacci(3));
}
}
代码语言:javascript复制Exception in thread "main" java.lang.StackOverflowError
at Test.fibonacci(Test.java:3)
at Test.fibonacci(Test.java:3)
at Test.fibonacci(Test.java:3)
OutOfMemory
下面是内存溢出的例子,内存泄露的例子不太好举。
代码语言:javascript复制class Test {
public static void main(String[] args) {
int[] test = new int[Integer.MAX_VALUE];
System.out.println(test.length);
}
}
Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
class Test {
public static void main(String[] args) {
int[] test = new int[Integer.MAX_VALUE-3];
System.out.println(test.length);
}
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
本地方法栈
作用与虚拟机栈非常相似,区别在于:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用的Native方法服务。在HotSpot虚拟机本地方法栈和Java虚拟机栈合二为一。
本地方法在执行的时候,在本地方法栈也会创建一个栈帧。用于存放本地方法的局部变量表、操作数栈、动态链接、出口信息(同虚拟机栈)。
方法执行完毕之后相应的栈帧也会被弹出并释放空间,也会出现StackOverFlow和OutOfMemoryError两种异常。
JVM运行流程
参考
Java Guide面试突击版,百度可得最新版本,这里可能有删减,增加和修正。