听说jvm是后端最难的部分,内容是非常的抽象底层,我先了解一下概念。
jvm的用途
主要是在java线上服务出问题的时候,用来排查问题,java字节码都是跑在jvm上。
第二个用途是写代码的时候,因为了解了jvm的禁忌,可以避免一些坑。
第三个用途是对高并发系统进行调优。
jvm内存结构
首先了解一下jvm里面的内存结构,java代码里面的对象是如何在内存中存储的。
jvm的内存结构称为 运行时数据区 runtime data area。
两类、五个区域。
共享区:
- 堆 heap
- 方法区/元空间 method area/metaspace
私有区
- 栈 VM stack
- 程序计数器 program counter register
- 本地方法栈 native method stack
堆 heap
jvm内存里面最大的一个区域,主要作用就是用来存放对象的实例,new出来的所有实例都放在这。
其中的所有线程是共享的。GC(垃圾收集器)主要处理的就是这里的对象实例。
方法区 method area
存储类的结构信息(包括controller、service如何定义的)、编译了方法之后生成的字节码、静态变量、常量。这些都存储在这。
元空间在jdk8及之后的版本,都是用的物理内存而不是jvm内存。这样是为了减少内存溢出的概率。
VM stack 虚拟机栈
用来管理java方法的执行,每一个线程都有一个栈。
这个好理解,和C语言的栈差不多。栈帧里面存储着方案的局部变量等。
局部变量创建的时候,就将其push到栈里,执行完方法,局部变量销毁的时候,就将其pop出栈。
常见的错误就是stackoverflow。栈被占满了,撑爆了。
程序计数器
记录当前线程执行到了哪一行字节码。
java代码编译成字节码在jvm里面执行,所以程序计数器也是记录的字节码的位置而不是代码的位置。
本地方法栈
和VM stack很像,区别是VM stack是管理java方法的,而本地方法栈,是管理native方法。
native方法就是java代码调用的那些底层的c/c++写的代码。
注意:常用的hotspot虚拟机里面,VM stack和native method stack合并了。
代码实例
@RestController
public class UserController {
// 【静态变量】:"name" 的引用和 "Java" 这个常量,存放在【方法区(元空间)】
private static String name = "Java";
@GetMapping("/getUser")
public void getUser() {
// 【局部变量】:'id' 是基本数据类型,直接存在【虚拟机栈】的方法栈帧里
int id = 10;
// 【局部变量+对象】:
// 1. 'user' 这是一个引用(类似指针),存在【虚拟机栈】里。
// 2. 'new User()' 这是实实在在的对象,存在【堆(Heap)】里!
User user = new User();
}
}
代码里面new出来的对象,都是放到堆里面。
局部变量、方法调用、都是放到VM stack的栈帧里面。
总结
堆:new对象、调优、垃圾回收、内存溢出。这些和堆相关。
栈:局部变量、方法调用、死递归、stackouverflow。这些和栈相关。