2.1概述
如果不了解虚拟机是怎样使用内存的,那么排查错误将会成为一项异常艰难的工作。
2.2运行时数据区域
2.2.1程序计数器
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
- 当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来确定下一条要执行的字节码指令的位置,对于多线程,每个线程都需要有一个独立的程序计数器(线程私有)。
- 执行 Java 方法和 native 方法时程序计数器中内容的区别:
- 执行 Java 方法时:记录虚拟机正在执行的字节码指令地址;
- 执行 native 方法时:为空
- 是 5 个区域中唯一不会出现 OOM(OutOfMemoryError) 的区域。
2.2.2Java虚拟机栈
线程私有,生命周期与线程相同。是描述Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
可能抛出的异常:
- OutOfMemoryError(在虚拟机栈可以动态扩展的情况下,扩展时无法申请到足够的内存);
- StackOverflowError(线程请求的栈深度 > 虚拟机所允许的深度);
2.2.3本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈作用相似,区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
可能抛出的异常:与虚拟机栈相同
2.2.4Java堆(GC堆)
是内存中最大的一块,被所有线程共享的区域,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,因此也叫GC堆
可以处于物理上不连续的内存空间中,逻辑上连续即可。
可能抛出的异常:OutOfMemoryError(堆中没有内存可以分配给新创建的实例,并且堆也无法再继续扩展了)。
2.2.5 方法区
线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;
- 类信息:即 Class 类,如类名、访问修饰符、常量池、字段描述、方法描述等。
- 垃圾收集行为在此区域很少发生
- 可能抛出的异常:OutOfMemoryError(方法区无法满足内存分配需求时)。
2.2.6运行时常量池
- 运行时常量池是方法区的一部分;
- 可能抛出的异常:OutOfMemoryError(方法区无法满足内存分配需求时)。
2.2.7直接内存
在 Java 堆中存储一个 DirectByteBuffer 对象作为堆外内存的引用,这样就可以对堆外内存进行操作了。
可能会出现 OutOfMemoryError的异常。
2.3HotSpot虚拟机对象探秘
2.3.1对象的创建
1.类加载检查
2.为对象分配内存,分配方式分为:
- 指针碰撞:使用中的内存在一边,未使用的内存在一边,通过移动指针来为对象分配内存。
- 空闲列表:维护一个空闲的列表
选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
3.将分配到的内存空间初始化为零值
4.虚拟机对对象进行必要的设置
5.执行<init>方法
2.3.2对象的内存布局
分为3个区域:对象头,实例数据,对齐填充
对象头:
包含:1.用于存储对象自身的运行时数据2.类型指针
实例部分:
对齐填充(不一定存在)
2.3.3对象的访问定位
两种主流方式:1.使用句柄2.直接指针
使用句柄:Java堆中的reference数据通过找到句柄再找到对象地址,特点:修改不繁琐
直接指针:reference数据直接找到对象地址,特点:快