本文主要是介绍JVM内存分配机制详解(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
JVM对象创建过程
1.类加载检查
流程:
- JVM在遇到一条
new
指令 - 检查这个指令的参数是否在常量池中定位到一个类的符号引用 【方法区】
- 并检查 符号引用 所代表的类是否被类加载器加载
包括 new关键字、对象克隆、对象序列化等等
**总的来说:**执行一个类加载的流程
2. 分配内存
定义:
Java虚拟机给新生对象分配内存,对象所需内存大小在类加载阶段可以完全确定。
分配方法:
方法 | 作用 |
---|---|
指针碰撞【默认】 | Java堆内存完全规整的前提, 维护一个指针 【已分配 | 未分配】 |
空闲列表 | Java堆内存不是规整的前提, 维护一个列表,记录内存块的可用情况 |
怎么解决多线程争抢同一块内存?
- CAS+失败重试 —— 保证分配内存空间的原子性
- 线程本地分配缓冲区 TLAB —— 给每个线程预先在堆中分配一小块内存,实现隔离。
3. 初始化零值
定义:
Java虚拟机将刚刚得到的内存区域全部初始化为零值
如果使用TLAB,则这一步在TLAB分配时就可以进行
4. 设置对象头
定义:
对象在内存中存储的布局可以分为3块区域
- 对象头
- 实例数据
- 对齐填充
对象头 == MarkWord + Klass Pointer类型指针
组成:
信息 | 作用 |
---|---|
哈希码 | HashCode |
GC分代年龄 | 经历了多少次GC |
锁状态标志 | synchronized的标志 |
偏向线程ID | 偏向锁偏向的线程ID |
偏向时间戳 | 偏向的时间 |
数组大小 | 默认是4字节 |
类型指针 Klass Pointer | 指向的类元数据的指针 【方法区】 指针压缩的作用点 |
MarkWord
5 执行<init>方法
定义:
按照程序员的意愿,进行初始化,对应到语言层面上讲,就是为属性赋值,并执行构造方法。
总结:
对象创建的主要流程:
- 首先进行类加载,不会重复加载
- 类加载完成后,给对象分配内存 【两种方式】
- 给对象初始化零值 【不是程序员赋值】
- 设置对象头 【设置对象各种信息】
- 执行<init>方法 【赋值并执行构造】
对象的指针压缩
定义:
在64位平台的JVM中,对Klass Pointer
开启压缩指针优化后,CPU可以最多访问 32G的堆内存 因为32位可以表示 2^32个内存地址
原理:
通过对对象指针的存入堆内存时,压缩编码、取出到cpu寄存器后解码方式进行优化,使得JVM使用32位地址,就可以支持更大的内存配置【32G】
对齐填充:
对于大部分处理器,对象以8字节整数倍来对齐填充都是最高效的存取方式。
为什么开启指针压缩后支持32G内存?
不开启指针压缩:
32位存储可以寻址4G【2^32】内存地址
开启指针压缩:
因为Java默认是8字节对齐的内存,一个对象占用的空间必须是8的整数倍。
所以,假如说2^32个内存地址,每一个代表8字节
那么最多可以描述2^32 * 8 = 32GB 内存
对象内存分配方式
对象的栈内分配
定义:
JVM通过逃逸分析,确定该对象不会被外部访问【不会逃逸出当前作用域】,那么就可以将该对象在栈上分配内存,减轻GC的压力
如果对象还能被进一步分解,【分解为各种基本类型】,JVM不会创建该对象,而是用标量替换这一个对象,在栈帧或寄存器中分配空间
例子:
public User test1() {User user = new User();user.setId(1);user.setName("zhuge");//TODO 保存到数据库return user; // 返回,逃逸出当前作用域,堆中分配内存
}public void test2() {User user = new User();user.setId(1);user.setName("zhuge");//TODO 保存到数据库// 没有返回,栈内分配内存
}
对象在堆中分配
对象在Eden区分配
定义:
大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC
分配准则:
让eden区尽量的大,survivor区够用即可
注意:
如果minorGC期间,一个对象太大无法存入Survior空间,这个对象会提前进入到老年代
大对象直接进入老年代
定义:
需要大量连续内存空间的对象,会直接进入老年代。
JVM参数:
//JVM参数 只在 Serial 和ParNew两个收集器有效
-XX:PretenureSizeThreshold //设置大对象大小阈值// 例子
-XX:PretenureSizeThreshold=1000000 (B) -XX:+UseSerialGC
对象动态年龄判断
定义:
针对Survivor区域里,如果一批对象的总大小大于这块Survivor区域内存大小的50%,那么这批对象会 直接进入老年代
对象动态年龄判断机制一般是在minor gc之后触发的
-XX:TargetSurvivorRatio可以指定大于的阈值 默认50%
老年代空间分配担保机制
定义:
在每一次minor GC
之前,JVM都会计算下老年代剩余可用空间,如果可用空间不足,查看担保参数,老年代空间是否小于担保参数,如果小于,则先进行一次full GC
,在进行minor GC
为什么要这样做?
因为老年代的空间可能不足,进行一次full GC
再做minor GC
会提高性能。
如果full GC
后老年代仍可用空间不足,会发生OOM
对象内存回收算法
引用计数法
定义:
给对象添加一个引用计数器,有地方引用它,它就+1,引用失效就-1
问题:
无法解决对象之间相互循环引用的问题
可达性分析算法
定义:
通过GC Roots对象作为起点,从节点开始向下搜索对象,标记对象为非垃圾对象,其余未标记的对象是垃圾对象
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等
finalize()方法
定义:
当对象被标记为不可达的对象,会被调用一次finalize()
方法
注意:
一个对象的finalize()
方法只会被执行一次,也就是说通过调用finalize()
方法自我救命的机会就一次
方法区内存回收
定义:
方法区主要回收的是无用的类,那么如何判断一个类是无用的类呢?
类需要同时满足下面3个条件才能算是 “无用的类” :
- Java 堆中不存在该类的任何实例
- 加载该类的
ClassLoader
已经被回收 - 该类对应的
java.lang.Class对象
没有被引用,无法通过反射访问该类的方法
这篇关于JVM内存分配机制详解(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!