【jvm】对象分配过程
目录
-
-
-
-
- [1. 基本步骤](#1. 基本步骤)
-
- [1.1 类加载检查](#1.1 类加载检查)
- [1.2 内存分配](#1.2 内存分配)
- [1.3 内存空间初始化](#1.3 内存空间初始化)
- [1.4 设置对象头](#1.4 设置对象头)
- [1.5 执行构造方法](#1.5 执行构造方法)
- [2. 优化策略](#2. 优化策略)
-
- [2.1 栈上分配](#2.1 栈上分配)
- [2.2 TLAB分配](#2.2 TLAB分配)
- [2.3 大对象直接分配到老年代](#2.3 大对象直接分配到老年代)
- [3. 具体实现方式](#3. 具体实现方式)
-
- [3.1 指针碰撞](#3.1 指针碰撞)
- [3.2 空闲列表](#3.2 空闲列表)
- [4. 垃圾回收与对象分配的关系](#4. 垃圾回收与对象分配的关系)
-
- [4.1 垃圾回收算法](#4.1 垃圾回收算法)
- [4.2 分代回收策略](#4.2 分代回收策略)
- [4.3 GC类型与触发条件](#4.3 GC类型与触发条件)
- [5. 过程剖析](#5. 过程剖析)
- [6. 对象提升(promotion)规则](#6. 对象提升(promotion)规则)
- [7. 内存分配原则](#7. 内存分配原则)
1. 基本步骤
1.1 类加载检查
- 1.当JVM执行new指令时,会首先检查所需的对象所属类是否已经被加载、解析和初始化。
- 2.如果类尚未加载,JVM会先执行类加载过程。
1.2 内存分配
- 1.在类加载完成后,JVM会在堆内存中为对象分配一块连续的内存空间。
- 2.内存分配的具体方式取决于JVM的内存管理策略和垃圾回收器的实现。
1.3 内存空间初始化
- 1.分配到的内存空间会被初始化为零值(对于对象类型的成员变量,会将其引用设置为null)。
1.4 设置对象头
- 1.对象头包含了对象的一些元数据,如哈希码、GC分代年龄、锁状态标志、对象类型指针等。
- 2.这些信息对于JVM的垃圾回收和对象管理至关重要。
1.5 执行构造方法
- 1.JVM会调用对象的构造方法,为对象的实例变量进行赋值,并执行其他初始化操作。
2. 优化策略
2.1 栈上分配
- 1.基于逃逸分析技术,JVM可以确定一个对象是否只在一个线程中使用且不会逃逸出方法作用域。
- 2.如果满足这些条件,JVM会尝试将对象分配到栈上而不是堆上。
- 3.栈上分配的对象会在方法调用结束后自动销毁,无需垃圾回收器介入,从而提高了性能。
2.2 TLAB分配
- 1.Thread Local Allocation Buffer,本地线程分配缓存。
- 2.TLAB是JVM为每个线程提供的一块专用的内存分配区域。
- 3.由于堆内存是线程共享的,多个线程同时分配对象时可能会产生竞争。
- 4.使用TLAB可以避免这种竞争,提高对象分配的效率。
- 5.由于TLAB空间有限,大对象无法进行TLAB分配,只能直接分配到堆上。
2.3 大对象直接分配到老年代
- 1.对于大对象(如大型数组或数据结构),JVM会直接将其分配到堆上的老年代区域。
- 2.这是因为大对象占用的内存空间较大,如果频繁进行复制或移动操作,会严重影响性能。
- 3.直接分配到老年代可以避免这种性能开销。
3. 具体实现方式
3.1 指针碰撞
- 1.当堆内存中的空闲内存与已使用的内存之间有一块完全空闲的区域时,JVM可以通过移动一个指针来分配内存。
- 2.这种方式简单且高效,但要求堆内存是连续的。
3.2 空闲列表
- 1.如果堆内存不是连续的,JVM会维护一个空闲列表来记录所有空闲的内存块。
- 2.当需要为对象分配内存时,JVM会遍历空闲列表,找到合适大小的内存块并将其从列表中移除。
- 3.这种方式虽然相对复杂,但可以处理内存碎片问题。
4. 垃圾回收与对象分配的关系
4.1 垃圾回收算法
- 1.JVM使用多种垃圾回收算法来管理和回收堆内存中的对象。
- 2.常见的垃圾回收算法包括标记-清除 、复制 、标记-整理 和分代回收等。
- 3.这些算法各有优缺点,JVM会根据实际情况选择合适的算法进行垃圾回收。
4.2 分代回收策略
- 1.JVM将堆内存分为年轻代 、老年代 和永久代 (或元空间)。
- 2.不同代上的对象具有不同的生命周期和回收策略。
- 3.年轻代上的对象通常是短生命周期的,而老年代上的对象则是长生命周期的。
- 4.永久代(或元空间)用于存放Java类的元数据信息。
4.3 GC类型与触发条件
- 1.JVM中的垃圾回收类型包括Minor GC(针对年轻代的垃圾回收)和Full GC(针对整个堆和永久代的垃圾回收)。
- 2.触发GC的条件包括年轻代被写满 、老年代被写满 、永久代被写满 以及显式调用System.gc()方法等。
5. 过程剖析
- 1.new的对象先放伊甸园区,该区有大小限制。
- 2.当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC/YGC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。
- 3.再加载新的对象放到伊甸园区。
- 4.然后将伊甸园中的剩余对象移动到幸存者0区。
- 5.如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的对象还没有被回收,则会放到幸存者1区。
- 6.如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。
- 7.如果幸存了15次,下一次垃圾回收未被回收则会放到老年代。默认15次,可以设置参数-XX:MaxTenuringThreshold=15,设置对象晋升老年代的年龄阈值。
- 8.在老年代,不会轻易变动。当老年代内存不足时,再次出发Major GC,进行养老区的内存清理。
- 9.若老年代执行了Major GC之后发现依然无法进行对象的保存,就会产生OOM异常java.lang.OutOfMemoryError:Java heap space。
- 1.如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。
- 2.对象在Survivor区中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,其实每个JVM、每个GC都有所不同)时,就会被晋升老年代中。
7. 内存分配原则
- 1.优先分配到Eden。
- 2.大对象直接分配到老年代,尽量避免程序中出现过多的大对象。
- 3.长期存活的对象分配到老年代。
- 4.动态对象年龄判断,如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreadhold中要求的年龄。