jdk1.8
JVM 内存共分为虚拟机栈、堆、元数据区、程序计数器、本地方法栈五个部分。
程序计数器:线程私有,它可以看做是当前线程所执行 的字节码的行号指示器。不会发生异常
虚拟机栈:线程私有,用于存储栈帧。每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等。
局部变量表:是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量(8种基本类型、对象引用和returnAddress类型)
操作数栈:是一个后入先出栈(LIFO)。随着方法执行和字节码指令的执行,会从局部 变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者 返回给方法调用者,也就是出栈/入栈操作
动态链接:Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了 支持方法调用过程中的动态链接(将符号引用转换成直接引用)
方法返回地址:程序正常退出或异常退出,都需要返回到方法被调用的位置,程序才能继续进行
本地方法栈:线程私有,与虚拟机栈所发挥的作用是非常相似的, 其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码) 服务, 而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务
堆:jvm共享,Java堆(Java Heap) 是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存,jdk1.8后静态变量和常量池等并入堆中。
元空间:存储类的元信息。最大可利用空间是整个系统内存的可用空间。
public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> oomObjectList = new ArrayList<>(); while (true) { oomObjectList.add(new OOMObject()); } } }
说明:不断向oomObjectList添加对象导致GC Roots 到对象之间有可达路径来避免垃圾收集回收机制清除这些对象。新生代满后,会发生Minor GC,GC完成后空间仍不足,将对象放入老年代,老年代空间不足会发生Full GC,之后如果空间还不足以存放新对象则抛 出 OutOfMemoryError 异常。
原因:
内存中加载数据过多
集合对对象引用过多且使用完后未清除
代码存在死循环产生过多重复对象
堆分配不合理
如果线程请求栈最大深度超过虚拟机所允许的最大深度,会抛出StackOverflowError异常。
如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常。