JVM 内存划分
1. 谈谈 JVM 内存区域的划分,哪些区域可能发生 OutOfMemoryError?
上图反应了实际中 Java 进程内存的占用。可以看到,与 JVM 运行时数据区还是有差别的。区别在于多了“直接内存区域”和 Code Cache 等。
JVM 本身是个本地程序,还需要其他的内存去完成各种基本任务,比如,JIT Compiler 在运行时对热点方法进行编译,就会将编译后的方法储存在 Code Cache 里面;GC 等功能需要运行在本地线程之中,类似部分都需要占用内存空间。这些是实现 JVM JIT 等功能的需要,但规范中并不涉及。
// jvm 规范
线程共享:堆、方法区。线程独享(即,每个线程有一份):栈、程序计数器
程序计数器:每个线程都拥有它自己的程序计数器,程序计数器存储的是正在执行的 Java 方法的 JVM 指令地址。
Java 虚拟机栈:方法调用伴随着栈帧的入栈和出栈,栈帧中存储着局部变量表、操作数(operand)栈、动态链接、方法正常退出或者异常退出的定义等。
堆:线程共享,用来放置 Java 对象实例。
方法区:线程共享,用于存储元数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等。
运行时常量池:运行时常量池是方法区的一部分。编译期生成的各种字面量、符号引用等,都存储在常量池中。
本地方法栈: Hotspot 中,本地方法栈和 Java 虚拟机栈是在同一块儿区域
2. 永久代问题。
// 过去,方法区有 perm 区实现。
JDK 7 以前,Intern 字符串的缓存和静态变量曾经都被分配在永久代上。而 JDK 8 将永久代移除,永久代被元数据区取代。需要注意的是,Intern 字符串缓存和静态变量并不是被转移到元数据区,而是直接在堆上分配。
因此,可以明确所有的对象实例都是创建在堆上。
2. 所谓 OOM,即,没有空闲内存,并且垃圾收集器也无法提供更多内存。
2. 除了程序计数器,其他区域都有可能会因为空间不足发生 OutOfMemoryError,简单总结如下:
堆:“java.lang.OutOfMemoryError:Java heap space”。原因可能是:存在内存泄漏问题、堆的大小不合理等
栈:当递归不断深入,JVM 会先抛出 StackOverFlowError,当 JVM 试图去扩展栈空间失败时,会抛出 OutOfMemoryError
方法区:“java.lang.OutOfMemoryError: Metaspace”
评论