Java 内存区域与内存溢出异常
1. 运行时数据区域
Java 虚拟机在执行Java 程序的过程中慧把它所管理的内存划分为若干个不同的数据区域。如下图所示:
(1).程序计数器:较小的内存空间,可以看作时当前线程所执行的字节码的行号指示器。(是唯一一个不会OOM 的区域)
(2).Java 虚拟栈:存储局部变量、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成,对应一个栈帧在虚拟机栈中入栈和出栈的过程。
(3).本地方法栈:类似与虚拟机栈,只不过两者执行的服务不一样,前者时虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,后者为虚拟机使用到的Native 方法服务。
(4).Java 堆(也叫GC 堆):虚拟机管理的内存中最大的一块,唯一目的就是存放实例对象。
(5).方法区:存储常量、静态变量等。
(6).运行时常量池:属于方法区的一部分
(7).直接内存:既不属于虚拟机也不是Java 虚拟机规范中定义的内存区域。NIO 的时候会用到
2. HotSpot 虚拟机对象
进一步了解这些虚拟机内存中的数据的其他细节。包括创建----布局----访问
(1).对象的创建
在语言层面上,创建对象(例如:克隆、反序列化)通常仅仅是一个new 关键字而已,而在虚拟机中对象创建要复杂的多,如下图所示:
(2).对象的内存布局:三块区域----对象头、实例数据和对齐填充
- 对象头
①用于存储对象自身的运行时数据:哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;
②对象指向它的类元数据的类型指针(但是不一定每个对象都有),如果对象时数组那么对象头中还包括一块用于记录数组长度的数据;
- 实例数据:是对象真正存储的有效信息,也是程序代码中所定义的哥中类型的字段内容;
- 对齐填充:不是必然存在,也没有特别的含义,仅仅起着占位符的作用;
(3).对象的访问定位:原理:栈上的reference 数据来操作堆上的具体对象,有两种方式----句柄和直接指针
- 句柄:Java 堆中划分一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,如下图所示
- 直接指针:Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象地址,如下图所示
- 两种访问的区别:句柄:reference记录的是句柄地址,对象改变时只需要改变句柄中实例数据的指针,不影响reference指向,更稳定;直接指针:直接访问对象,相对句柄访问中间少了一层句柄池,因此更快,但是开销会积少成多,增加执行成本。
3. OutOfMemoryError 异常
(1).Java 堆溢出:使用Eclipse Memory Analyzer 分析堆转储快照文件,详细见Eclipse Memory Analyzer,内存泄漏插件,安装使用一条龙
(2).虚拟机栈和本地方法栈溢出:
①如果线程请求的栈深度大于虚拟机所运行的最大深度,将抛出StackOverflowError 异常(内存泄漏)
②如果虚拟机在扩展时无法申请到足够的内存空间,则抛出OutOfMemoryError 异常(内存溢出)
(3).方法区和运行时常量池溢出
(4).本机直接内存溢出
4. 总结
1. 虚拟机中的内存是如何划分
2. 异常产生的原因
原文地址:https://www.cnblogs.com/hellovoyager1/p/9153434.html