@
目录1. JVM内存分区简介
JVM内存分区如图所示:
主要有如下几个区域:
- 栈(Stack)
- 堆(Heap)
- 方法区(Method Area)
- 程序计数器(PC)
- 本地方法栈(Native Method Stack)
其中,程序计数器用于存储线程当前执行的指令地址(记录进度),程序计数器是线程私有的;
本地方法栈并不是每个JVM都必须实现,而是针对支持native本地方法调用的JVM。
- 本地方法:使用非Java语言定义的方法;
- 本地方法栈同一般的JVM栈一样,可分配固定或者动态内存。
剩余的三个区域:栈、堆、方法区,在下文中会详细介绍。
2. JVM栈
Java栈的基本存储单元为栈帧,每个线程对应一个栈帧,不同栈帧之间不会共享数据。
栈帧主要包括:
- 局部变量:在方法内部定义的变量
- 操作数栈:函数形参
- 指向运行时常量池的引用
- 方法返回地址
- 附加信息
JVM栈的内存大小可以固定设置也可以动态分配,当出现栈溢出的时候,固定内存的栈会抛出StackOverFlowError
,而动态分配的栈则会抛出OutOfMemoryError
。
3. JVM堆
JVM堆中存放的是对象实例(说人话就是用new创建的对象,因为数组也是new int[]
这种形式创建的,所以JVM将数组看作一种特殊的对象)。
JVM堆是线程共享的,可动态分配内存。
需要注意的是,当一个方法结束之后,JVM栈中的相关数据会出栈,释放内存,但是对于JVM堆,虽然创建对象的方法已经消失,但是对象本身并不会在堆中被销毁,而是会等待垃圾回收机制进行内存回收。
4. JVM方法区
JVM方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法区是线程共享的。
方法区中有个区域叫做常量池
,包括静态常量池
和运行时常量池
。
静态常量池存放的是编译时就能够确定的常量数据,包括一些常数、类信息、方法信息等等;
运行时常量池存放的是运行时才能够确定的常量数据。
需要注意的是,运行时常量池在JDK1.7之后不再放在方法区中,而是放在堆中。
因此,对于常见的字符串常量池
,如果一个字符串在编译时可以确定值,那么就放在方法区
的静态常量池
中,如果在运行时遇到字符串先查找常量池,常量池中没有该字符串,则该字符串被创建,创建的对象会放在运行时常量池中,放在方法区还是堆中就需要根据版本来确定。
5. JVM内存分配实例
public class Demo {
String username;
public void method() {
int i=1;
System.out.println("执行类方法");
}
public static void main(String[] args) {
int i=1;
String str="hello java";
Demo demo=new Demo();
demo.username="123";
demo.method();
}
}
//出处:https://blog.csdn.net/m0_57816620/article/details/127332057?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1-127332057-blog-125451232.235%5Ev38%5Epc_relevant_anti_t3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-1-127332057-blog-125451232.235%5Ev38%5Epc_relevant_anti_t3&utm_relevant_index=2
对于上面的Java代码,各位可以思考一下内存分配的具体情况。
- Demo类编译的时候产生.class文件,放在方法区的
静态常量池
中,存储类信息; - 从Demo类的main方法进入,方法
main
的内存地址入栈, int i=1;
创建一个局部变量i
,值为1
,入栈;String str="hello java";
创建一个字符串变量str
,入栈;对于hello java
这个字符串常量,JVM查找静态常量池
和运行时常量池
中是否存在该字符串,如果存在,字符串变量str
直接指向该字符串常量,如果不存在,则创建一个字符串常量hello java
,并将该字符串常量放入运行时常量池
中,然后字符串变量str
指向该字符串常量;Demo demo=new Demo()
创建一个对象变量demo
,入栈;对于对象实例本身,则会创建之后存于堆中,然后demo
变量指向该对象实例;demo.username="123"
修改类中的属性值,在堆中设置username
变量,值为123
;demo.method()
调用类方法,方法定义存于JVM方法区中,取出该方法信息,然后进入method
方法,开启新一个线程,JVM栈帧加1,存放method
方法的内存地址;- 执行
method
方法,int i
直接将局部变量入栈; System.out.println("执行类方法");
则是调用System.out.println
方法(库方法);method
方法执行完毕,栈帧出栈;main
方法执行完毕,栈帧出栈。- 栈中无栈帧,结束进程;而此时堆中依然保留
Demo
对象实例,等待垃圾回收机制回收。
面试模拟
Q:JVM堆、栈、方法区的作用
A:JVM栈存放方法调用时的局部变量、形参、返回地址等信息,每个方法调用的时候都会产生一个栈帧,出入栈操作实现方法的嵌套调用;
堆中存放的是对象实例或者数组,是线程共享的,可动态分配内存;
方法区存放的是一些静态变量和常量数据,包括类编译的方法和属性信息,静态常量池和运行时常量池在JDK1.6均属于此类。