一、运行时数据区域
1. java内存区模型
image.png注 :
1. 绿色方框的数据区由所有线程共享
2. 运行时数据区的白色区域线程隔离
2. 程序计数器
2.1 程序计数器是java线程里运行到哪一条指令的指示器,当线程开始运行或者线程恢复运行,程序计数器便开始被修改,在程序的分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器来完成。
2.2 每个线程都有自己的一个程序计数器,它是线程隔离的。
3. java虚拟机栈
3.1 虚拟机栈是一块执行线程方法的内存区域。
3.2 虚拟机栈描述的事java方法执行的线程内存模型:每个方法被执行的时候,java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
数据类型在局部变量表中的存储空间以局部变量槽来表示,其中64位长度的long和double类型的数据会占用两个变量槽(所以long和double在并发修改下,数据值会产生不可预知的值,因为多个线程可能在修改两个槽的其中一个),其余的数据类型只占用一个。
4. 本地方法栈
本地方法栈与虚拟机所发挥的作用非常相似,只是本地方法栈是为逊尼基使用到的本地方法服务
5. java堆
5.1 java堆是虚拟机里最大的、线程共享的一块内存区域。
5.2 虚拟机的垃圾回收主要是在java堆上执行的,几乎所有的对象实例都是在这里分配内存的。
5.3 从jdk7开始,常量池也已迁移到java堆中
5.4 堆的具体描述,将在后续的学习过程中展开。
6. 方法区
6.1 方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息,常量、静态变量、即时编译器编译后的代码缓存等数据。
二、HotSpot虚拟机对象探秘
1. 对象的创建
当java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
- 1.1 分配内存:
对象所需内存的大小在类加载完成后便可完全确定
对象内存分配方式有两种方式,分别是指针碰撞和空闲列表方式[见下面注释]
对象内存分配时,因为是修改一个指针位置,因此会存在并发问题,虚拟机有两种方式解决指针并发问题,分别是CAS配上失败重试和TLAB[见下面注释]
- 1.2 设置初始值:
内存分配完成之后,虚拟机必须将分配到的内存空间都初始化为零值,如果使用了TLAB的话,这一项工作也可以提前至TLAB分配时顺便初始化。
注:
1. 指针碰撞:假设java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式成为指针碰撞。
2. 空闲列表:如果内存不是规整的,已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单的进行指针碰撞,虚拟机必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式成为空闲列表。
3.TLAB:每个线程在java堆中预先分配一小块内存,称为TLAB,哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓冲区时才需要同步锁定。
2. 对象的内存布局
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头、实例数据、对齐填充。
3. 对象的访问定位
3.1 对象的访问定位分为两种,分别为直接指针访问和句柄访问
3.2 直接指针访问是在栈帧里直接通过对象的引用访问对象数据
3.3 句柄访问是在java堆中维护着一个对象句柄和对象指针的映射关系,把对象引用的访问通过这个映射表隔离开了
三、实战:OutOfMemoryError异常
1. java堆溢出
虚拟机配置参数:
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
package error;
import java.util.ArrayList;
import java.util.List;
public class HeapOOM {
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while(true) {
list.add(new OOMObject());
}
}
static class OOMObject {}
}
- 1.1 处理内存溢出的简略思路
检查内存泄漏或者内存溢出。
- 内存泄漏: 如果有对象已经不在GCRoots引用链上面,但是一直没被回收
- 内存溢出:对象都是不可回收的。检查虚拟机的堆参数配置,对象的存储结构是否合理,是否需要长时间的生命周期
2. 虚拟机栈和本地方法栈溢出
3. 方法区和运行时常量池溢出
虚拟机配置参数:
-XX:PermSize=6M -XX:MaxPermSize=6M -Xmx6M
package error;
import java.util.HashSet;
import java.util.Set;
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
short i = 0;
while(true) {
set.add(String.valueOf(i++).intern());
}
}
}
网友评论