JVM
2024-12-05 09:41:59 0 举报
JVM内存结构
作者其他创作
大纲/内容
10
2bit
对象内存布局
重量级锁
程序计数器
年龄
对齐填充
1bit
Epoch偏向锁版本
对象分代年龄
状态
32位JVM MarkWord内存模型
GC
对象头内存布局
偏向锁
1
MarkWord
内存分配完毕之后,虚拟机需要将除了对象头之外的内存空间设置零值,才能保证实例化字段不用赋值即可使用,使java程序能访问到对应对象的初始值,接下来会对象进行必要的设置,比如:这个对象是哪个类的实例、对象的hash值(实际上对象的hash值只有在首次调用Object::hashCode()方法之后才会确定)、对象的GC分代年龄信息等等,这些信息存放在对象头中,然后将对象引用入栈(这里入栈指的是地址值赋值给栈中在编译时期就存在的对象引用)
是否偏向锁
0
直接内存:直接内存不属于虚拟机划分的内存部分,但是也会被频繁使用,在jdk1.4新加入了NIO,引入了一种基于通道和缓冲区的IO方式,它能使用Native方法直接分配堆外内存,然后通过堆内的一个名为DirectByteBuffer对象(作为堆外内存的引用)来使用堆外内存,避免了在堆和堆外来回复制数据,需要注意的是,直接内存也是内存,也存在于本机内存中,所以在设置jvm参数时也需要考虑到直接内存的大小,不要因为忽略了直接内存大小导致直接内存+jvm分配的内存大于了总内存,引发OOM
虚拟机栈
锁变化顺序
4bit
抢到锁的线程ID
分配后处理
分配空间
01
指向栈帧中锁记录的指针
堆
JVM规定了每个对象的大小一定是8字节的倍数,对象头也严谨的按照了此规定来设置,那么当整个对象的大小不足8N的时候,就需要通过对齐填充来补足。
64位JVM MarkWord内存模型
方法区
和堆一样,都是线程共享的内存空间,用于储存已经被虚拟机加载的类型信息、常量、静态变量等数据,因为最早期HotSpot设计团队为了能让垃圾回收器能像回收堆一样去回收方法区,所以用永久代的方式实现方法区,所以一直有人说方法区实际上也是堆的一部分,但是方法区实际上是no-heap的。是不等价的。
虚拟机栈(栈)
线程私有,指的是java方法执行的线程内存模型,每个方法的调用都会创建一个栈帧用于储存方法信息,局部变量表等信息,然后调用过程就是栈帧出入栈的过程。局部变量表储存的是在编译期间就可知的基本数据类型和reference类型(这个并不是对象本身,对象本身在堆中存在,这里指的是这个对象的引用类型,可能是一个指向实际对象的指针,也可能是一块代表对象的代码块)和 returnAddress类型(指向一条字节码指令的地址)
本地方法栈(栈)
和虚拟机栈是非常相似的,可以理解为,虚拟机栈是为了执行java方法字节码存在的,而本地方法栈是为了执行native本地方法字节码而存在的
1.虚拟机管理的最大的、线程共享的内存,该内存存在的唯一目的就是存放对象实例,几乎所有的对象实例都存在于堆中,但是由于语言的更新迭代,在将来的更新中,可能会有其他值类型的支持,所以未来也可能出现实例存放在了其他的地方,2.堆的空间管理是GC完成的,这里具体理论详解在后面贴出3.堆在物理内存中并不一定是连续的,但是在逻辑上我们认为他是连续的,但对于某些大的对象,比如数组,大多数的虚拟机会为了内存分配的效率和管理效率进而要求物理连续空间4.堆既可以是固定大小,也可以是可扩展的,由jvm参数控制,当其已经到了最大的限制并且无法扩展时,会抛出OOM异常
一块比较小的内存空间,线程私有,多个线程间的计数器互不干扰,可以看作为当前线程所执行的字节码的行号指示器,代码中各种循环,跳转,异常处理等等操作都需要用到计数器来完成
常量池
常量池是方法区的一部分,用于存放在编译期生成的各种字面量和符号引用
未使用
本地方法栈
11
对象的实例化
分配内存空间就是在堆中划分一块足够大的内存去存放这个对象,分配的方式有两种,第一种:如果堆中的空间是规整的(用过和未使用的完全分开,中间有一个指针为临界点),那么此时划分空间只需要指针移动对应对象大小的距离即可,这种称之为指针碰撞。第二种:如果堆空间不规整,那么虚拟机就必须维护一个列表,这个列表记录了空间的内存块,分配空间时,从list中找到足够大的内存块划分给对象,这种就称之为空闲列表。
25bit
00
实例数据
31bit
线程共享
无锁
56bit
线程不共享
锁标识位
轻量级锁
54bit
在并发情况下,可能出现正在给对象A分配内存,还没有完成的时候,对象B也要用原来的指针分配内存,线程并不是安全的,有两种解决方案,第一种;堆分配内存的动作进行同步处理,就是CAS配上失败重试机制来保证原子性操作。第二种:将划分内存的动作按照线程划分在不同的内存块中,就是每个线程在堆中都先预先分配一小块内存缓冲区(TLAB),只有缓冲区用完了,才会进行同步锁定操作,进行新的缓存区划分,是否使用TLAB可以通过jvm参数来控制
对象头
当遇到new指令时,首先去方法区常量池中找到正确的类的符号引用,如果找不到,就要执行对应的类加载过程,如果找到了,那么就开始对象分配内存操作,分配的空间是在类加载时就已经确定的
JVM内存模型
对象的哈希值
检查引用
lenth(只有数组对象才会有值)
23bit
对齐补充
压缩指针(指向了该实例对应的栈中的类引用)
并发问题
收藏
收藏
0 条评论
下一页