概述
java程序员把内存控制的权利交给了虚拟机,一旦出现内存泄漏和溢出问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会成为一项异常艰难的工作
运行时数据区域
程序计数器
- 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。
- 每条线程都需要有一个独立的程序计数器,各个线程之间的程序计数器互补影响,独立存储我们成这类内存区域未线程私有的内存
- 次内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
java虚拟机栈
- java虚拟机栈是线程私有的,生命名周期与线程相同。
- 每个方法被执行的时候都会创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息
- 虚拟机栈存放了编译器的8中基本数据类型,对象引用(它根据不同的虚拟机来实现,可能是一个指向初始对象的一个引用指针,可能指向一个代表对象的句柄或其他与此对象相关的位置) 和returnAddress(指向一条字节码的地址)
- 局部变量所需要的内存在编译器已经确定并且完成分配,不会做出改变。
- 异常:请求的栈深度大于虚拟机规定的深度,将抛出StackOverflowError异常。如果虚拟机栈可动态扩展,当它无法申请到内存将会抛出OutOfMemoryError异常
本地方法栈
- 与java虚拟栈非常相似,不过它为本地方法也就是native服务。
- 不同的虚拟机会有不同的实现
- 也会抛出StackOverflowError与OutOfMemoryError异常
java堆
- 线程共享
- 对象,数组,存储的时候并不需要连续的地址,只需要逻辑上连续就可以。
- 随着JIT编译器的发展与逃匿分析技术的逐渐成熟,所有对象都分配在堆上变得不是那么绝对了。
- 垃圾收集器的主要管理区域。
- 当它无法扩展时会抛出OutOfMemoryError异常
方法区(在jav1.8 改为元空间)
- 是被各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、即时编译器编译后的代码。
- 相对而言垃圾收集器在这个区域是比较少出现的,区别于永久带,数据回收主要针对常量池的回收和对类型的卸载,回收的效率是低下的。
- 当它无法扩展时会抛出OutOfMemoryError异常
运行时常量池
- 它是方法区的一部分,用于存储编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量中。
- 运行时常量池也可以在运行时产生并且加入到运行时常量池中。
- 和方法区一样,无法扩展时会抛出OutOfMemoryError异常
直接内存
- 不是java虚拟机定义的内存区域
- 在nio中的channel和Buffer的I/O 方式,它使用native函数库直接分配堆外内存,然后通过一个存储在java中堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样在一些场景中显著的提高了性能。避免了java中和native来回复制数据。
对象访问
Object obj = new Object()
1. Object obj 这部分语义会反映到java栈中本地变量表,作为一个引用数据类型出现
2. new Object这部分语义会反映到java堆内存中,形成一块存储了obj所有的数据的内存空间
3. java栈访问java堆分为两种不同的方式,句柄和指针
此处在java堆中多了一个句柄池,它包含了指向所有具体信息的指针
此方式的对象分配内存的方式较为简单
实现OutOfMemoryError异常
/**
* VM options:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
*/
public class OutOfMemoryMain {
public static void main(String[] args) throws CloneNotSupportedException {
List<Object> list = new ArrayList<>();
while (true) {
list.add(new Object());
}
}
}
- 添加java虚拟机运行参数 -Xms20M -Xmx20M
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at outofmemory.OutOfMemoryMain.main(OutOfMemoryMain.java:16)
虚拟机栈和本地方法栈溢出
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将会抛出StackOverflowError
我们使用递归来产生异常
public class StackFlowError {
public static void main(String[] args) {
round();
}
public static void round() {
round();
}
}
以下是异常信息
Exception in thread "main" java.lang.StackOverflowError
at outofmemory.StackOutOfMemory.addI(StackOutOfMemory.java:13)
at outofmemory.StackOutOfMemory.addI(StackOutOfMemory.java:13)
at outofmemory.StackOutOfMemory.addI(StackOutOfMemory.java:13)
at outofmemory.StackOutOfMemory.addI(StackOutOfMemory.java:13)
at outofmemory.StackOutOfMemory.addI(StackOutOfMemory.java:13)
- 如果虚拟机在扩展栈时无法申请到足够的内存空间,将会抛出OutOfMemoryError
循环创建线程并且保证每条线程都是活着运行的
public static void main(String[] args) {
while (true) {
Thread thread = new Thread(() -> {
while(true) {
}
});
thread.start();
}
}
}
注意上述代码执行的时候回造成电脑死机,执行前保存一下当前的工作状态
方法区溢出
在之前称为方法区,从java8开始称为:元空间
- 这个空间已经被完全的移除
- JVM参数PermSize和MaxPermSize参数被忽略
/**
* Created by 10250 on 2018/7/7.
* vm param -XX:MaxMetaspaceSize=1m
* 方法区溢出
*/
public class MethodArea {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
// output
error occurred during initialization of VM
OutOfMemoryError: Metaspace
元空间(metaspace)的内存分配模型
- 现在大多数类元数据分配在本地化内存中
- 我们用来描述类的classes已经被移除
元空间的容量
- 默认情况下,类元数据分配收到本机可用的内存限制(容量亦然取决于你使用32位操作系统还是64位操作系统)
- 一个新的参数(MaxMetaspaceSize)可以使用,它用来限制类元数据存储空间,就是以前的方法区。如果没有指定,在程序运行时将会自动设置
元数据的垃圾回收
- 如果当前运行的元数据大于MaxMetaspaceSize的值,将会触发对垃圾回收器对该区域的回收,但是当不能满足回收条件,并且还在申请内存时,将会抛出异常
直接内存溢出
public class DirectMemoryTest {
public static final int MB = 1024*1024;
public static void main(String[] args) throws IllegalAccessException {
Field field = Unsafe.class.getDeclaredFields()[0];
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
while (true){
unsafe.allocateMemory(MB);
}
}
}
// 异常信息
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at outofmemory.DirectMemoryTest.main(DirectMemoryTest.java:23)