一、运行时数据区
JVM在执行Java程序的时候,将其运行时数据区划分为若干不同区域。它们的用途和创建及销毁的时间不同。
1、程序计数器(Program Counter Register)
是一块很小的内存空间。当线程执行的是Java方法,它记录的是当前正在执行的字节码指令的地址;当线程执行的是Native方法,则它的值为空。
2、虚拟机栈(Java Virtual Machine Stacks)
它描述的是Java方法执行的内存模型:每个方法在执行的时候都会创建一个栈帧,用来存储变量表、操作数栈、动态链接和方法出口等信息。每个方法
从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
3、本地方法栈(Native Method Stack)
顾名思义,它和虚拟机栈的区别是:它为本地方法的执行提供服务,虚拟机栈为Java方法的执行提供服务。
Hotspot不区分虚拟机栈和本地方法栈。
4、堆(Heap)
它是JVM所管理的内存中最大的一块,随虚拟机启动而创建。它唯一的作用就是存放对象实例,几乎所有的对象实例和数组都是在堆上分配,所以是GC的主要管理区域。
5、方法区(Method Area)
又称作非堆(None-Heap),这个称谓在JConsole等工具中很常见。它用于存储已经被JVM加载的类信息、常量、静态变量以及即时编译器编译后的代码等。
♣运行时常量池 方法区中除了存放跟类相关的信息之外,还有一部分是运行时常量池。它用来存放编译器产生的字面量和符号引用。 int i = 1; //将1赋值给变量i,那么1就是字面量 String s = "abc"; //字符串abc也是字面量 字面量包括整数、浮点数、字符串有些语言的字面量还包括布尔型。 在编译时, Java类并不知道所引用的类的实际地址, 因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道 Language类的实际内存地址, 因此只能使用符号org.simple.Language来表示Language类的地址,org.simple.Language就是所谓的符号引用。 运行时常量池的作用是节省空间和时间。例如字符串常量池,在编译阶段就把所有的字符串放到一个常量池中,可以避免频繁的创建和销毁对象而影响系统性能,同时实现了对象的共享。 |
二、对象的创建
对象的创建一般是在new、对象克隆和反序列化的时候。
三、对象的内存布局
四、对象的访问定位
一般地,对象的访问定位方式有两种:通过句柄访问和通过直接指针访问。
JVM在调用一个方法的时候,会在虚拟机栈上创建栈帧,其本地变量表中会包含对象的reference。
1、通过句柄访问:
在堆上划出一部分作为对象句柄池,句柄中包含两个指针—对象实例数据指针和对象类型数据指针。reference用句柄作为跳板,来访问对象的实例数据(堆上)
和对象类型数据(方法区里)。
2、通过直接指针访问:
堆上不单独划句柄池,在给对象分配的内存中包含对象实例数据和类型数据指针。通过reference直接访问对象实例数据,通过类型数据指针作为跳板来访问
方法区中的对象类型数据。Hotspot使用这种方法。
五、内存溢出
1、堆溢出
堆溢出会报错java.lang.OutOfMemoryError。
首先要区分是泄露还是溢出,泄露一般是由于某些原因导致对象脱离了GC的管理。如果是溢出,可以考虑优化虚拟机的堆参数(Xms和Xmx)。
可以通过内存映射分析工具Eclipse Memory Analyzer进行分析,找到解决方案。
2、虚拟机和本地方法栈溢出
原因有两个:①线程请求栈深度超过虚拟机允许的最大值,会报错java.lang.StackOverFlow;
②栈空间的额度给的太小,会报错java.lang.OutOfMemoryError。
3、方法区和运行时常量池
原因是运行时加载了大量的类信息填满了方法区导致溢出,会报错java.lang.OutOfMemoryError。
【参考文章】
https://blog.csdn.net/BraveLoser/article/details/82500474
https://blog.csdn.net/BraveLoser/article/details/82500474