总结分享 | JVM内存结构
这里结合一段 java 代码的执行理解内存划分
- 执行 javac 命令编译源代码为字节码
- 执行 java 命令
- 创建 JVM,调用类加载子系统加载 class,将类的信息存入方法区
- 创建 main 线程,使用的内存区域是 JVM 虚拟机栈,开始执行 main 方法代码
- 如果遇到了未见过的类,会继续触发类加载过程,同样会存入方法区
- 需要创建对象,会使用堆内存来存储对象
- 不再使用的对象,会由垃圾回收器在内存不足时回收其内存
- 调用方法时,方法内的局部变量、方法参数所使用的是 JVM 虚拟机栈中的栈帧内存
- 调用方法时,先要到方法区获得到该方法的字节码指令,由解释器将字节码指令解释为机器码执行
- 调用方法时,会将要执行的指令行号读到程序计数器,这样当发生了线程切换,恢复时就可以从中断的位置继续
- 对于非 java 实现(Native)的方法调用,使用内存称为本地方法栈
- 对于热点方法调用,或者频繁的循环代码,由 JIT 即时编译器将这些代码编译成机器码缓存,提高执行性能
说明:
- 对于 Oracle 的 Hotspot 虚拟机实现,不区分虚拟机栈和本地方法栈
程序计数器:
线程私有的(每个线程都有一个自己的程序计数器), 是一个指针. 代码运行, 执行命令。而每个命令都是有行号的,会使用程序计数器来记录命令执行到多少行了,记录代码执行的位置
Java 虚拟机栈:
线程私有的(每个线程都有一个自己的 Java 虚拟机栈). 一个方法运行, 就会给这个方法创建一个栈帧, 栈帧入栈执行代码, 执行完毕之后出栈(弹栈)存引用变量,基本数据类型
本地方法栈:
线程私有的(每个线程都有一个自己的本地方法栈), 和 Java 虚拟机栈类似, Java 虚拟机栈加载的是普通方法,本地方法加载的是 native 修饰的方法。
native:在 java 中有用 native 修饰的,表示这个方法不是 java 原生的。
堆:
线程共享的(所有的线程共享一份)。存放对象的,new 的对象都存储在这个区域。还有就是常量池。
元空间:
存储.class 信息, 类的信息,方法的定义,静态变量等。而常量池放到堆里存储JDK1.8 和 JDK1.7 的 jvm 内存最大的区别是, 在 1.8 中方法区是由元空间(元数据区)来实现的常量池。
1.8 不存在方法区,将方法区的实现给去掉了。而是在本地内存中,新加入元数据区(元空间)
补充
不会出现内存溢出的区域
- – 程序计数器
会出现内存溢出的区域
- 出现 OutOfMemoryError 的情况(内存不足)
- 堆内存耗尽 – 对象越来越多,又一直在使用,不能被垃圾回收
- 方法区内存耗尽 – 加载的类越来越多,很多框架都会在运行期间动态产生新的类
- JVM虚拟机栈累积 – 每个线程最多会占用 1 M 内存,线程个数越来越多,而又长时间运行不销毁时;一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小时;
- 出现 StackOverflowError 的区域(栈溢出错误)
- JVM 虚拟机栈,原因有方法递归调用未正确结束、反序列化 json 时循环引用