1、HotSpot虚拟机对象探秘

1.1、对象的创建

当遇到new指令时候,先检查指令的参数能否在常量池中得到一个类的符号引用、若没有,必须先执行相应的类加载过程

 

为新生对象分配内存,大小在加载完成后就能确定

 

从java堆中划分出等内存大小的区域,若java堆中的内存绝对规整,在空闲与使用过的内存中间有一个指针,那么分配内存就是将指针像空闲空间方向挪动与对象大小相等的距离,这个叫做 指针碰撞

若不规整,那么需要维护一个列表,记录内存块状态,找到足够大的内存块分配给对象实例,这叫 空闲列表

 

 

以上两种取决于使用哪种的GC(带压缩与不带压缩)

 

还要考虑对象创建行为是否频繁,在并发情况下线程不安全

解决1、分配空间动作同步处理 保证更新操作的原子性

解决2、按照线程规划在不同空间进行:即每个线程预先分配一小块内存(本地线程分配缓冲(TLAB))只有缓存用完了,分配新缓冲区才需要锁定

 

 

内存分配完成后,虚拟机必须把分配到的内存空间(不包括 对象头)初始化为零值,若使用了TLAB,此工作可以提前完成

接下来java虚拟机还需要对对象进行必要设置

 

对于java程序视角看,class文件的()方法还没执行 构造函数

 

1.2、对象的内存布局

对象头、实例数据、对齐填充

 

对象头包括两信息:1、储存自身运行数据 (HashCode、GC分代年龄、锁状态标志...)

这数据长度在未开启压缩指针的虚拟机种分别为32比特与64比特(32位与64位) 称之为 Mark Word :动态的数据结构

2、类型指针、对象指向它的类型元数据指针,通过该指针可以分辨是哪个类的实例,但并不是所有的虚拟机必须在对象数据保存类型指针!

 

如果实例对象是数组,还有一个记录数组长度的指针!

 

 

实例数据:对象真正存储的有效信息:各自字段、父类继承下来的

 

对其填充:

不是必要存在,没有特别含义,起到占位符作用(任何对象必须是8字节的整数倍)

对象头精心设计成8的整数倍1~2倍

1.3对象的访问定位

使用该对象

Java程序通过栈上的 reference操作堆上的具体对象

 

如果使用句柄访问:java划分一块内存来作为句柄池,reference 存储的是对象的句柄地址,句柄中包含对象实例数据与类型数据各自具体的地址信息

使用指针访问:reference存储的是对象地址 (速度快,主要使用此方法)

2、OOM异常

1、堆溢出

最常见

常规处理办法:

内存映射分析工具对Dump出来的堆转储快照进行分析

1、先确认导致OOM的对象是否必要

分清楚内存泄漏还是内存溢出

 

内存泄露:有不必要的对象,查看GC回收器为什么无法回收他们

内存溢出:检查java虚拟机的堆参数是否有向上调整的空间(Xmx、Xms),再检查有哪些对象生命周期过长、设计不合理,减少程序运行期的小号

 

2、虚拟机栈和本地方法栈溢出

1、如果线程请求栈深度大于虚拟机所允许最大深度,抛出StackOverflowError异常

2、如果虚拟机允许栈内存动态扩展、如果扩展容量无法申请足够内存,就会OOM

HotSpot虚拟机不允许动态扩展

3、方法区和运行时常量溢出

常量区溢出

String::intern()是一个本地方法,作用是如果字符串常量池已经包含一个等于此String对象的字符串,则返回String对象的引用,否则将String对象包含的字符串添加到常量池中,并且返回此String对象的引用

String.valueOf(i++).intern

 

 

6版本以下常量池在方法区(永久代)的一部分

方法区溢出

一个类要被GC回收,条件很苛刻

8版本,元空间彻底取代永久代

4、本机直接内存溢出

直接内存(Direct Memory)

容量大小可以通过-XX:MaxDirectMemorySize参数指定,默认与java堆最大值(Xmx)一致

 

直接内存导致溢出明显特征是在Heap Dump文件中不会看见有什么明显的异常情况,如果发现内存溢出后Dump文件很小,而且使用了DirectMemory( 例如NIO),可以考虑是否直接内存溢出