JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
- 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
- Java虚拟机主要分为五大模块:类装载器子系统、运行时数据区、执行引擎、本地方法接口和垃圾收集模块。
- 其中垃圾收集模块在Java虚拟机规范中并没有要求Java虚拟机垃圾收集,但是在没有发明无限的内存之前,大多数JVM实现都是有垃圾收集的。
- 而运行时数据区都会以某种形式存在于每一个JAVA虚拟机实例中,但是Java虚拟机规范对它的描述却是相当抽象。
内存管理
Java虚拟机在执行程序的过程中会把它所管理的内存划分为几个不同的区域,如下图:
注意: Java虚拟机管理的内存就是由图中几个运行时的数据区域组成,分为方法区、java堆、java栈、本地方法栈、程序计数器。其中,java栈、本地方法栈和程序计数器是线程隔离的区域,是线程私有的。
五大内存区域:
一、方法区
- 方法区是所有线程共享的内存区域。
- 方法区用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。
- 方法区中的信息来源于类装载子系统,类装载子系统加载class信息,加载到的东西就存放于方法区的内存空间。
- 这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。
运行时常量池
是方法区的一部分,class文件除了有类的字段、接口、方法等描述信息之外,还有常量池用于存放编译期间生成的各种字面量和符号引用。
二、Java堆
- Java堆是所有线程共享的内存区域。
- 堆在JVM启动的时候会被创建,我们可以把它理解成一个内存池,用来存放所有的java对象实例。
- 根据虚拟机规范,Java堆可以存在物理上不连续的内存空间,就像磁盘空间只要逻辑是连续的即可。它的内存大小可以设为固定大小,也可以扩展。
- 同时它也是GC所管理的主要区域,因此常被称为GC堆。
- 并且java堆是完全自动化管理的,通过垃圾回收机制,垃圾对象会被自动清理,而不需要显示的释放。
三、Java栈(虚拟机栈)
- 每一个线程都有一个私有的Java栈。
- Java栈在线程创建的时候被创建,其中保存着帧信息。(每个方法在执行的同时都会创建一个栈帧,用于保存局部变量、方法参数、方法出口等信息)
- 每一个方法从调用到执行完成的过程,都对应着一个栈帧在Java栈中入栈到出栈的过程。
- 通常所说的栈,一般是指在虚拟机栈中的局部变量部分。
栈帧: 是用来存储数据和部分过程结果的数据结构。
栈帧的位置: 内存 -> 运行时数据区 -> 某个线程对应的虚拟机栈 ->
栈帧大小确定时间: 编译期确定,不受运行期数据影响。
Java栈可能出现两种类型的异常:
(1)线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
(2)虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。
四、本地方法栈
- 本地方法栈是线程私有的。
- 本地方法栈和Java栈非常类似,不同之处在于Java栈用于java方法(字节码)的调用。
- 本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++(我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码)。
五、程序计数器
- 程序计数器是线程私有的。
- 程序计数器是一块很小的内存空间,可以认作为当前线程的行号指示器,也叫指令计数器。
- 如果当前线程正在执行一个java方法,则程序计数器记录正在执行的java字节码地址,如果当前线程正在执行一个本地方法(native底层方法),那么程序计数器为空。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。
为什么需要程序计数器?
- 对单核CPU来说,每一时刻只能有一个线程处于运行状态,直到当前线程执行完或者被挂起,让出CPU资源之后,才会轮到下一个线程进来运行。
- 如果是多核CPU,是可以同时跑多线程的,线程与线程之间也是互不相干,当线程数量大于CPU数量时,也会和单核CPU一样进行一个轮训切换来分配资源,为了确保线程切换后能够恢复到正常的位置继续执行,每个线程就必须要有自己的程序计数器,程序计数器是线程独有的一块内存空间。
- 不同线程之间的程序计数器互不影响,独立存储。