为什么要关注jvm内存划分
因为线上系统写好代码,部署之后,工程师就需要开始关注OOM,GC这些jvm的东西了,这就需要对jvm内存有一定了解才能做好这些工作。
区域划分
可以划分成两类:
- 一类是线程私有的:程序计数器,虚拟机栈,本地方法栈。
- 另一类是线程共享的:堆,方法
通过代码来理解更直观 ,比如简单的HelloWorld程序
public class HelloWorld {
pubic static void main(String[] args) {
System.out.println("hello world");
}
}
这一段helloworld的代码首先存在后缀为.java 的文件当中,这就是java源代码文件,不过.java文件是给程序员看的,计算机看不懂。需要进行编译,把java文件编译成.class后缀的的字节码文件,class文件里存放的是编译好的字节码,字节码才是计算机可以理解的一种语言。
字节码指令可能会让计算机从内存里面读取某个数据,或者把数据存入到内存,各种各样的字节码指令,让计算机做各种事情。
程序计数器
在执行字节码指令单 时候,jvm里面的程序计数器就是用来记录每一个线程当前执行的字节码指令到哪一个位置了,记录当前线程执行到了哪一个字节码指令。因为以为会有很多个线程并发地执行各种不同的字节码,所以每一个线程都需要有一个属于自己的程序计数器。
java虚拟机栈
java代码在执行的时候,都是由线程来执行一个方法中的代码,比如在helloworld代码中,一个main线程来执行main方法中的代码。
如果在一个方法中定义一个局部变量,比如
public void sayHello() {
String name = "xiaoming";
}
定义一个String类型的局部变量,jvm内部就需要一个区域来保存方法内部的局部变量还有其他数据,这个区域就是java虚拟机栈。
由于一个线程会执行各种方法的代码,方法内部还会嵌套的执行其他方法的代码,所以每一个线程都需要又一个属于自己的java虚拟机栈。
如果一个线程执行了一个方法调用,就会给这个方法调用创建一个栈帧,栈帧里面存放的是局部变量表,操作数栈这些东西。比如又一个线程调用sayHello()方法,就会给sayHello()方法创建一个栈帧,压入java虚拟机栈当中。如果在sayHello()方法里面再调用其他方法sayWelcome(),也会给sayWelcome()方法创建一个栈帧,压入java虚拟机栈当中。
如果sayWelcome()方法执行完了,就会从java虚拟机栈中吧他的栈帧出栈。入股接下来,sayHello()方法也执行完了,就把sayHello()方法的栈帧也从java虚拟机出栈。
java堆
java堆是jvm里面很重要的一个区域,里面存放着在代码中创建的各种对象。比如
public void sayHello(String name) {
Student student = new Student(name);
student.study();
}
在方法内部创建一个Student对象,new Student(name)这个操作就是创建了Student类型的对象实例,这个对对象实例里面会包含一个数据。比如name,就是数据这个对象实例的数据,对象实例就是存放在java堆当中的。
这样java堆内存区域会存放Student这样的对象,sayHello方法的栈帧里面的局部变量表存放在java虚拟机栈当中,局部变量表当中存放了student局部变量,存放的事Student对象的地址,这样java虚拟机栈当中的局部变量就指向了java堆内存里面的Student对象。
方法区
方法区主要存放的是类的相关信息,jdk1.8之后,这各区域叫metaspace,叫元数据区更合适,存放的还是类相关的信息。
本地方法栈
java底层的有些API,比如IO,NIO,网络SOCKET相关的,很多地方都不是java代码,会通过native方法调用底层操作系统的一些方法,当调用native方法的时候,就会有线程对应的本地方法栈。
本地方法栈和java虚拟机栈很相似,存放的事native方法的局部变量表相关的信息。