JVM内存管理机制----对《深入理解JAVA虚拟机》第二章的理解(上)

来源:互联网 发布:伦纳德死亡缠绕 知乎 编辑:程序博客网 时间:2024/05/22 22:34

布局

JVM内存区域分为五块:虚拟机栈,本地方法栈,程序计数器,堆区,方法区。

虚拟机栈(线程私有):生命周期与线程周期一致。执行java方法,线程中每执行一个方法都会创建一个栈帧。方法调用时,栈帧在JVM中入栈,方法结束时,出栈。

本地方法栈(线程私有):执行native等非java语言的方法。

程序计数器(线程私有):指示当前线程所执行字节码到了第几行,执行的是java代码的时候,记录虚拟机字节码地址,执行native时,返回undefind。这个位置是JVM唯一没有标出内存溢出的位置。

堆区(线程共享):存储对象实例。

方法区(线程共享):用于存储虚拟机加载的类信息,final常量,静态变量,编译器即时编译代码等。包含一个运行时常量池

一、对象创立:

1.类加载并检查

1)能否在常量池中定位类的符号引用

2)检查类是否已经被加载过

2.进行内存分配

1)开辟内存

java内存是绝对规整的,不存在已用内存和为用内存交错排布

所以可能是这样的(意淫)


这样指针就成为已用内存和未用内存分界点的指示器。这种方式被称为“指针碰撞”,他存在的条件式已用空间和为用空间已经区分规整好。

还有与指针碰撞相对立方式,叫做“空闲列表”,这种分配方式的存在条件是已用空间与为用空间没有做过整理,那就需要一个列表去记录每块空间干什么用的是否是未用过的。

2)开辟完内存后,虚拟机会将内存空间初始化为0(这样保证java在不用赋初始值的情况下使用)。

3)设置对象信息

二、如何保证内存分配的原子性(开辟出的空间只给某一个线程使用)

1、CAS+失败重试

CAS简单原理描述:现有内存值A  旧的预期内存值B  要修改的内存值C,当A==B则 A=C

当多线程的情况下则循环判断,这样效率很低,因为需要循环判断,再赋值

2、分配TLAB(本地线程分配缓冲)

也就是建立多线程时预先开辟内存空间

因为在TLAB上开辟空间不需要加锁,所以性能上高效些

三、对象的内存布局

三大块:对象头、实例数据、对齐填充

1)、对象头

对象头分为两部分:mark word、类型指针

mark word主要存储的是运行时数据,里面有哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等。

mark word是非固定的数据结构。原因是对象头里面的信息不属于对象本身的实际数据,个人感觉这部分信息是给JVM看的,说明的是加载来的这个对象的性质以及乱七八糟的东西,所以不应该占用太大的空间去保存描述信息,所以在32位虚拟机中留出的32bit的空间,在64位虚拟机中留出的是64bit的空间。但是实际项目里的往往这些信息会超出给定的这点儿空间,所以用的是非固定数据结构。非固定的数据结构会根据对象的状态复用自己的存储空间,从图中我理解的意思就是不涉及到的状态信息他的位置将将让位给用的状态信息,让他拥有更大的空间去记录信息。

32位虚拟机状态

64位虚拟机状态


类型指针:对象指向类元素的指针。目的是确定对象是哪个类的实例。当然判断对象是哪个类不止类型指针一种方式。

书中给的例子说:如果对象是一个JAVA数组,对象头必须有一块用于记录数组长度的数据,虚拟机可以从java对象元数据中获取信息确定java对象的大小,但是从数组的元数据就无法确定数组大小。

个人是联想到咱们创建数组的时候,都是要给定数组的长度的,或许这个长度就是元数组可记录的长度。但是,数组本身既可以存储String、int等类型,也可以存储对象,这个对象在数组里面虽占一个对象的位置,但是本身的大小在数组里面是很难确定的,需要进入这个对象里面才能确定。所以指针不会作为唯一判断,应该还有其他方式去确定数组的长度。

2)实例数据

顾名思义这里面应该就是有效的信息,他不仅记录了各种类型字段内容,并且还记录了这些类型字段她来自哪,关系是什么。

这部分的存储顺序收到虚拟机分配策略和java源码中定义的顺序影响。

3)对齐填充

这部分主要起的是占位符的作用,当写入的实力数据不足以填满开辟的空间时需要对齐填充。为什么这么写?原因是HotSpot VM的自身内存管理系统要求对象的起始地址必须是8字节的整数倍。



0 0
原创粉丝点击