JVM内存布局
java数据结构
- 静态成员变量
- 类的常量
- 动态成员变量
- 局部变量(区域变量)
- 短小紧凑的对象声明
- 庞大复杂的内存申请
java内存区域划分
java 7 及其以前内存划分:
方法区:线程共享,JVM启动时创建,关闭时销毁。存放类的信息,静态变量,类常量池,字符串常量池
堆:线程共享,JVM启动时创建,关闭时销毁。存放类的实例
Java虚拟机栈:线程私有,跟程序计数器配(记录方法执行字节码行号-字节码行号记录器;执行引擎线程切换时记录切换线程执行的字节码行号)对出现。线程执行方法时,创建对应的栈,执行完毕后销毁。有局部变量表,操作数栈,动态链接,返回地址
本地方法栈:线程私有,执行本地方法。会有报错OutOfMemoryError:当前线程申请栈时,发现栈已经满了,内存也用光。
程序计数器:线程私有,记录java方法时字节码指令的地址;执行引擎进行线程切换时记录线程java方法执行的位置;不存在内存溢出情况。不记录本地方法栈相关信息
- java 7 内存模型图
Java 8内存模型
方法区:线程共享,JVM启动时创建,关闭时销毁。存放类的信息,静态变量,类常量池,运行时常量池
堆:线程共享,JVM启动时创建,关闭时销毁。存放类的实例,字符串常量池(执行String.intern()方法的位置)
Java虚拟机栈:线程私有,跟程序计数器配(记录方法执行字节码行号-字节码行号记录器;执行引擎线程切换时记录切换线程执行的字节码行号)对出现。线程执行方法时,创建对应的栈,执行完毕后销毁。有局部变量表,操作数栈,动态链接,返回地址
本地方法栈:线程私有,执行本地方法。会有报错OutOfMemoryError:当前线程申请栈时,发现栈已经满了,内存也用光。
程序计数器:线程私有,记录java方法时字节码指令的地址;执行引擎进行线程切换时记录线程java方法执行的位置;不存在内存溢出情况。不记录本地方法栈相关信息
-
java 8 内存模型
-
从JVM内存区域所示,得出:
JVM堆中数据时共享的,是内存最大的一块区域
可以执行字节码的模块叫执行引擎
执行引擎在线程切换时恢复依靠的程序计数器
JVM内存划分与多线程息息相关。运行时用的栈,以及本地方法栈,维度都是线程
本地内存包含元数据区和一些直接内存。
java虚拟机栈
- 上文提到,java虚拟机栈基于线程,main方法也是以线程方式运行的。线程生命周期中,参与计算的数据会频繁的入栈和出栈,栈的声明周期和线程一样
- 会在线程申请栈时报StackOverflow,是因为在申请栈时,已经超出栈的最大深度,但此时内存空间还有很多;会在内存用完的时候,线程申请栈时报OutOfMemoryError
- 组成部分
局部变量表:存放方法的局部变量
操作数:
动态链接
返回地址
- java虚拟机栈
本地方法栈
- 与java虚拟机栈非常相似的区域,服务对象是native方法。甚至可以认为虚拟机栈和本地方法栈是同一个区域,者并不影响对JVM了解。
- 特殊数据类型-returnAddress
1.returnAddress:存在于字节码层面。对于JVM来说,程序就是存储在方法区的字节码指令,而returnAddress类型的值就是指向特定内存地址的指针。
2.这里有两层栈:第一层对于方法,第二层是方法执行,对应着操作数。
3.可以看到,所有字节码指令,其实都会抽象成对栈的入栈出栈操作。
执行引擎只需要傻瓜式的按顺序执行,就可以保证他的正确性。
- java-本地方法栈-returnAddress
程序计数器
-
记录java方法执行字节码的地址,配合虚拟机栈完成计算操作。存储当前正在运行流程流程,包括正在执行的指令,跳转,分支,循环,异常处理等
-
执行引擎进行线程切换时,依靠对应线程的程序计数器来找到上次执行字节码的地址,接着执行。线程CPU获取CPU时间片是不可预知的,需要程序计数器,对线程正在运行的地位进行缓冲记录,以便在获取CPU时间片是能够快速恢复。
-
较小内存区域,不会引起内存问题,其作用可以看成记录当前线程执行字节码的行号指示器。存的是当前线程执行的进度。
Java方法程序计数器 -
javap 输出的字节码,每个opcode都有一个序号,即红框的偏移地址,可认为是程序计数器内容
堆
- JVM最大内存区域,大部分对象都在此存储,GC进行操作的区域就是堆
- 在JVM启动时创建,关闭时销毁,不一定会全部使用其空间
- 随着对象创建增大,堆空间占用增多,需要不定期对不使用对象进行回收,即GC
- 对象大小不一,长时间运行,会产生细小空间碎片,造成空间浪费,进行对象回收的同时,还要进行堆空间整理
- 对象分配的位置
一般的:
1.基本数据类型(byte,short,int,long,float,long,boolean)分配在栈上
2.普通对象,JVM会在堆上创建对象,然后在其他对象使用的是它的引用,如将引用保存在虚拟机栈的局部变量表,类似int数组的类型非基本数据类型,也在堆上。
特别的:
1.jvm会判断对象是否存在线程逃逸,如果不存在就直接在栈上分配对象
-
java-方法中对象引用
- 堆是线程共享的,如果是多个线程访问,会涉及数据同步问题。
元空间
- 不同于PermGen,其在堆外进行内存分配,非堆内存,使用操作系统内存,不再存在内存溢出。但无限制使用操作系统内存会造成操作系统的死亡。
- 使用 -XX:MaxMetaspaceSize控制元空间大小
- 是一个概念,依然存在,物理存储的容器,即Metaspace
- 包含类的信息,运行时常量池,方法数据,方法代码
- java 方法区与元空间对比
(8)小结 - 字符常量存放位置
Java 7 及以前是在PermGen;Java8后在堆上
- 堆、非堆、本地内存关系
堆是软绵绵,松散有弹性;非堆冷冰生硬,非常紧凑。