一、简介
java程序是由JVM(Java 虚拟机)执行的,在执行过程中将内存划分为多个不同的区域
上图所示,首先,JVM不认识文本文件,由Java编译器编译后变成二进制.class文件,然后由JVM中的类加载器记载到JVM中,JVM进行对程序的内存管理。
二、JVM内存管理
JVM的内存结构也就是JVM的运行时数据区域
根据JVM《Java 虚拟机规范》的规定,JVM将内存划分成如下几个区域
1、程序计数器(Program Counter Register)
2、Java虚拟机栈(Java Virtual Machine Stack)
3、本地方法栈(Native Method Stacks)
4、Java堆(Java Heap)
5、方法区(Method Area)
2.1 程序计数器
作用:用于保存JVM中下条指令的地址
Java虚拟机的多线程是通过线程的切换实现的,每个线程都有独立的程序计数器。为了线程切换后能恢复到正确的执行位置,此时我么就需要一块区域来保存当前线程的执行信息
特点:
-
线程私有
- CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码
- 程序计数器是每个线程所私有的,当另一个线程的时间片用完,又返回来执行当前线程的代码时,通过程序计数器可以知道应该执行哪一句指令
-
不会存在内存溢出
JVM指令的运行过程:
2.2 Java虚拟机栈
每个线程被创建的时候,都会创建一个虚拟机栈,每次方法被调用时,都会创建一个栈帧。栈帧中包含方法参数、局部变量、返回地址等,而每个线程只能有一个活动栈帧,对应着当前正在执行的方法
栈的结构:
演示:
public class Main {
public static void main(String[] args) {
method1();
}
private static void method1() {
method2(1, 2);
}
private static int method2(int a, int b) {
int c = a + b;
return c;
}
}
2.3 本地方法栈
一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法
2.4 Java堆
堆区主要用于存放对象实例及数组,通过new关键字创建出的对象都会被放在堆内存中
特点
- 所有线程共享,堆内存中的对象都需要考虑线程安全问题
- 有垃圾回收机制
2.5 方法区
- 方法区用于存储虚拟机加载的类信息(类的版本、字段、方法、接口),常量,静态变量,即时编译器编译后的代码等数据。如果方法区无法满足新的内存分配需求时,将抛出OutOfMeMoryError异常。
- PerGen(永久代):绝大部分的程序员都应该见过"java.lang.OutOfMemoryError:PreGen space"异常,这里的PermGen space其实就是指的是方法区,但是上面我们已经说了方法区和永久代有着本质的区别,前者是JVM的规范,而后者则是JVM规范的一种实现,只有HotSpot才有PermGen space,由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。 JDK1.7以前使用永久代来实现方法区
- 元空间:
其实,移除永久代的工作从JDK1.7就开始了,JDK1.7中,在存储永久代的部分数据就已经转移到Java Heap 或者Native Heap,但永久代仍存在于JDK1.7中,并没有完全移除,比如符号引用(Symbols)转移到了Native Heap,字面量(interned strings)转移到Java Heap,类的静态变量(class statics)转移到了Java Heap。元空间存储不在虚拟机中,而是使用本地内存,JVM 不会再出现方法区的内存溢出