目录
一、JVM基本介绍——概念、组成、重点
- 1.1 JVM是什么
- 1.2 JVM由哪些部分组成,运行流程是什么?
- 1.3 JDK、JRE、JVM 关系
- 1.4 学习什么
二、JVM组成
- 2.1 什么是程序计数器
- 2.2 你能详细地介绍堆吗
- 2.3 什么是虚拟机栈
- 2.4 垃圾回收是否涉及栈内存
- 2.5 栈内存分配越大越好吗
- 2.6 方法内的局部变量是否线程安全
- 2.7 什么情况下会导致栈内存溢出
- 2.8 堆栈的区别是什么
- 2.9 介绍下方法区
- 2.10 直接内存
- 2.11 总结
javap -v xx.class #打印堆栈大小,局部变量的数量和方法的参数
一、JVM基本介绍——概念、组成、重点
1.1 JVM是什么
JVM(Java Virtual Machine,即java虚拟机),java程序的运行环境(java二进制字节码的运行环境)。
JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。针对java用户,也就是拥有可运行的.class文件包(jar或者war)的用户。里面主要包含了jvm和java运行时基本类库(rt.jar)。rt.jar可以简单粗暴地理解为:它就是java源码编译成的jar包。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java能够“一次编译,到处运行”的原因。
JVM是Java跨平台的关键,因为它屏蔽了不同操作系统之间的差异,可以让相同的Java程序在不同的操作系统上运行出相同的结果。
好处:
- 一次编写,到处运行
- 自动内存管理,垃圾回收机制
1.2 JVM由哪些部分组成,运行流程是什么?
JVM由哪些部分组成:类加载子系统,运行数据区(方法区、堆、程序计数器、虚拟机栈、本地方法栈),执行引擎(解释器、即使编辑器、垃圾回收)、本地库接口
JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载器)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地库接口)
Class loader(类加载器):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到运行时数据区中的方法区;
Execution engine(执行引擎):执行引擎也叫解释器,负责解释命令,交由操作系统执行;
Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
Runtime data area(运行时数据区域):这就是我们常说的JVM的内存,我们所有写的程序都被加载到这里,之后才开始运行。由五部分组成,Method Area/MateSpace 方法区/元空间、Heap 堆、PC Register 程序计数器、JVM Stacks 虚拟机栈、Nativa Method Stacks 本地方法栈
运行流程、作用 :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内;而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
GC垃圾回收主要针对 运行数据区中的堆空间。
1.3 JDK、JRE、JVM 关系
我们在 JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别 中已详细介绍过JDK、JRE、JVM的联系与区别,此处简单讲解下:
JDK(Java Development Kit,Java开发工具包)、JRE(Java Runtime Environment,Java运行时环境)、JVM(Java Virtual Machine,即java虚拟机)。
JDK是 Java 语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。
- JDK包含了JRE,JRE包含了JVM。
- 如果只想运行Java程序,只需安装JRE即可(少数情况例外);如果想要开发Java程序,则需要安装JDK。
- JDK是用于java程序的开发,而jre则是只能运行class而没有编译的功能;Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。
1.4 学习什么
二、JVM组成
2.1 什么是程序计数器
程序计数器:线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。
javap -v xx.class #打印堆栈大小,局部变量的数量和方法的参数
2.2 你能详细地介绍堆吗
线程共享的区域:主要用来保存对象实例,数组等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
Java堆主要组成部分:
- 元空间保存的类信息、静态变量、常量、编译后的代码(Jdk1.8引入)
- 年轻代被划分为三部分,Eden区和两个大小严格相同的Survivor区,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到老年代区间。
- 老年代主要保存生命周期长的对象,一般是一些老的对象
总结:你能详细地介绍Java堆吗?
- 线程共享的区域:主要用来保存对象实例,数组等,内存不够则抛出OutOfMemoryError异常
- 组成:年轻代+老年代
- 年轻代被划分为三部分,Eden区和两个大小严格相同的Survivor区
- 老年代主要保存生命周期长的对象,一般是一些老的对象
- Jdk1.7和1.8的区别
- 1.7中有有一个永久代,存储的是类信息、静态变量、常量、编译后的代码
- 1.8移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出
2.3 什么是虚拟机栈
Java Virtual machine Stacks (java 虚拟机栈),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧(stack Frame) ,对应着一次次的Java方法调用。一次方法的调用,就是栈帧入栈到出栈的过程;
虚拟机栈是线程私有的,生命周期和线程一致,其作用为 主管Java程序的运行,它保存方法的局部变量(8大基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回
每个线程都有自己的栈,栈中的数据都是以栈帧(stack Frame)的格式存在。栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息,存储:局部变量表(Local variables)、操作数栈(operand stack) (或表达式栈)、动态链接(Dynamic Linking) (或指向运行时常量池的方法引用)、方法返回地址(Return Address) (或方法正常退出或者异常退出的定义)、一些附加信息
- 每个线程运行时所需要的内存,称为虚拟机栈,先进后出
- 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存。栈帧对应的方法执行完后,栈会将该方法对应的栈帧弹出栈,释放内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
多线程下
public class StackTest {
public static void main(String[] args) {
StackTest stackTest = new StackTest();
stackTest.methodA();
}
public void methodA() {
int i = 10;
int j = 20;
methodB();
}
private void methodB() {
int k = 30;
int m = 40;
}
}
补充:本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的。其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
2.4 垃圾回收是否涉及栈内存
垃圾回收主要指就是堆内存,不涉及栈内存,当栈帧弹栈以后,内存就会释放。
1)什么是垃圾回收?
垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效地使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
2)结合2.5,因为栈帧对应的方法执行完后,栈会将该方法对应的栈帧弹出栈,释放内存,因此垃圾回收不涉及栈内存。
2.5 栈内存分配越大越好吗
未必,默认的栈内存通常为1024k
栈帧过大会导致线程数变少,例如,机器总内存为512m,目前能活动的线程数则为512个(512m/1024k=512);如果把栈内存改为2048k,那么能活动的栈帧就会减半(512m/2048k=256)
(栈内存的大小不会影响方法执行的速度,而且由于计算机硬件的储存大小是有限的,栈空间内存设置过大,创建线程数量较多时会出现栈内存溢出OutofMemoryError,导致最大线程数减少,得不偿失。同时,栈内存也决定方法调用的深度,栈内存过小则会导致方法调用的深度较小,如递归调用的次数较少)
2.6 方法内的局部变量是否线程安全
- 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
2.7 什么情况下会导致栈内存溢出
- 栈帧过多导致栈内存溢出。典型问题:递归调用,没有结束语句,一直递归调用方法,导致栈帧过多、栈内存溢出
- 栈帧过大导致栈内存溢出。单个栈帧的所需要的内存超出了栈内存大小
public static void m4(){
m4();
}
java.lang.StackOverflowError
2.8 堆栈的区别是什么
- 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会。
- 栈内存是线程私有的,而堆内存是线程共有的。
- 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。
- 栈空间不足:java.lang.StackOverFlowError。
- 堆空间不足:java.lang.OutOfMemoryError。
2.9 介绍下方法区
- 方法区(Method Area)是各个线程共享的内存区域
- 主要存储类的信息、运行时常量池
- 虚拟机启动的时候创建,关闭虚拟机时释放
- 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace
常量池
可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
javap -v Application.class #查看字节码结构(类的基本信息、常量池、方法定义)
运行时常量池
常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
2.10 直接内存
直接内存:并不属于JVM中的内存结构,不由JVM进行管理。是虚拟机的系统内存,常见于 NIO 操作时,用于数据缓冲区,它分配回收成本较高,但读写性能高。
举例:Java代码完成文件拷贝
常规IO的数据拷贝流程
NIO数据拷贝流程
2.11 总结
1)什么是程序计数器
线程私有的,每个线程一份,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。
2)你能详细地介绍Java堆吗
- 线程共享的区域:主要用来保存对象实例,数组等,内存不够则抛出OutOfMemoryError异常
- 组成:年轻代+老年代
- 年轻代被划分为三部分,Eden区和两个大小严格相同的Survivor区
- 老年代主要保存生命周期长的对象,一般是一些老的对象
- Jdk1.7和1.8的区别
- 1.7中有有一个永久代,存储的是类信息、静态变量、常量、编译后的代码
- 1.8移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出
3)什么是虚拟机栈
每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧(stack Frame) ,对应着一次次的Java方法调用。一次方法的调用,就是栈帧入栈到出栈的过程。
- 每个线程运行时所需要的内存,称为虚拟机栈,先进后出
- 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存。栈帧对应的方法执行完后,栈会将该方法对应的栈帧弹出栈,释放内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
4)垃圾回收是否涉及栈内存
垃圾回收主要指就是堆内存,不涉及栈内存,当栈帧弹栈以后,内存就会释放。
5)栈内存分配越大越好吗
未必,默认的栈内存通常为1024k,栈帧过大会导致线程数变少。
6)方法内的局部变量是否线程安全
- 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
7)什么情况下会导致栈内存溢出
- 栈帧过多导致栈内存溢出。典型问题:递归调用,没有结束语句,一直递归调用方法,导致栈帧过多、栈内存溢出
- 栈帧过大导致栈内存溢出。单个栈帧的所需要的内存超出了栈内存大小
8)堆栈的区别是什么
- 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会。
- 栈内存是线程私有的,而堆内存是线程共有的。
- 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。
- 栈空间不足:java.lang.StackOverFlowError。
- 堆空间不足:java.lang.OutOfMemoryError。
9)解释一下方法区
- 方法区(Method Area)是各个线程共享的内存区域
- 主要存储类的信息、运行时常量池
- 虚拟机启动的时候创建,关闭虚拟机时释放
- 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace
10)介绍一下运行时常量池
- 常量池:可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
- 当类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
11)直接内存
- 并不属于JVM中的内存结构,不由JVM进行管理。是虚拟机的系统内存
- 常见于 NIO 操作时,用于数据缓冲区,分配回收成本较高,但读写性能高,不受 JVM 内存回收管理
参考 黑马程序员相关视频与笔记