系列文章目录
第一节 JVM类加载机制详解
JVM对象创建、对象内存分配、对象内存回收机制
- 系列文章目录
- 前言
- 一、对象的创建
-
- 1.类加载检查
- 2、分配内存
-
- 划分内存的方法
-
- 指针碰撞(Bump the Pointer)(默认用指针碰撞)
- 空闲列表(Free List)
- 解决并发问题的方法
-
- CAS原子算法(Compare And Swap)
- 本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)
- 三、初始化零值
- 4.设置对象头
-
- (1)、32位对象头
- (2)64个对象头
- (3)代码案例
-
- A、Object对象头分析
- B、数组对象头分析
- C、Student实例对象头分析
- (4)对象指针压缩
-
- 为什么需要压缩指针?
- 5、执行方法
- 二、对象内存分配
-
- 1.在对象栈上分配
-
- 对象逃逸分析的含义
- JVM参数设置
-
- (1)标量替换的含义
- (2)标量和聚合量的含义
- 代码案例
-
- A、打开逃逸分析和标量替换
- B、关闭逃逸分析,打开标量替换
- C、打开逃逸分析,关闭标量替换
- 2.年轻时内存分配
-
- Minor GC和Full GC的区别
- Eden与Survivor区默认8:1:1
- 代码案例
-
- (1)、空跑
- (2)、Eden区放满
- (3)、触发Minor GC
- (4)、Eden对象再次放入区域
- 3.老年内存分配
-
- (1)大对象直接进入老年
-
- 代码案例
- (2)长期存活的对象将进入老年
- (3)对象的动态年龄判断
- (4)老年空间分配的担保机制
- 三、对象内存回收
-
- 引用计数法
- 可达性分析算法
- 常见的引用类型
-
- (1)强引用
- (2)软引用
- (3),弱引用
- (4)虚引用
- finalize()最终判断对象是否存活
-
- 注意事项
- 无用类判断
- 总结
前言
本节介绍JVM对象创建、内存分配、内存回收机制,对象创建包括类加载检查、内存分配、初始化、对象头设置、执行init方法;对象内存分配包括对象栈上的分配(逃逸分析和标量替换)Eden介绍老年内存分配;对象内存回收包括引用计数法和可达性分析,以及四种常见的引用类型—强引用、软引用、弱引用和虚引用。
一、对象的创建
1.类加载检查
遇到一个虚拟机会new在指令中,首先检查该指令的参数是否可以在常量池中定位为类的符号引用,并检查该符号引用代表的类是否已加载、分析和初始化。如果没有,则必须首先执行相应的类加载过程。new指令对应于语言层面,new关键字、对象克隆、对象序列化等。
2、分配内存
类加载检查通过后,虚拟机将为新对象分配内存。在类加载完成后,可以完全确定对象所需内存的大小。为对象分配空间的任务相当于 确定大小的内存从一起Java堆中分开。
- 如何划分内存
- 并发, 可能是内存分配给对象A,指针没有时间修改,对象B同时使用原指针分配内存
划分内存的方法
有两种方法可以划分内存,一种是指针碰撞,另一种是空闲列表,两种方法的区别在于堆内存是否规则。
“指针碰撞”(Bump the Pointer)(默认用指针碰撞)
如果Java堆中内存是,所有使用过的内存都放在一边,空闲内存放在另一边,指针作为分界点的指示器放在中间,分配的内存只是将指针移动到与物体大小相等的空闲空间。
空闲列表(Free List)
如果Java堆中的内存,使用的内存与空闲内存交错,因此没有办法简单地碰撞指针,JVM必须使用虚拟机,记录哪些内存块是可用的,在分配时从列表中找到足够大的空间分配给对象实例, 并更新列表上的记录
解决并发问题的方法
CAS原子算法(Compare And Swap)
虚拟机采用CAS配备失败重试的算法和方法,保证更新操作的原子性,同步处理分配内存空间的动作。
本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)
把之中进行,即每个线程在Java在堆中预先分配一小块内存。通过-XX: /-UseTLAB设置虚拟机是否使用参数TLAB(JVM会默认开启-XX: UseTLAB),-XX:TLABSize 指定TLAB大小。
三、初始化零值
内存分配完成后,虚拟机需要分配(不包括对象头), 。这一步确保了对象的实例字段Java可直接使用代码中的初始值,程序可以访问这些字段的数据类型对应的零值。
4.设置对象头
- 初始化零值后,虚拟机需要设置对象,例如对象是什么类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。
- 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data)和对齐填充(Padding)。
- HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
(1)、32位对象头
(2)、64位对象头
(3)代码案例
maven依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
JOL打印三种类型的对象头信息,分别为Object对象、数组对象、Student实例对象。
public class JolExample {
public static void main(String[] args) {
//Object对象
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
//数组对象
System.out.println();
System.out.println(ClassLayout.parseInstance(new int[]{
}).toPrintable());
//Student对象
System.out.println();
System.out.println(ClassLayout.parseInstance(new Student()).toPrintable());
}
static class Student{
int id;
String name;
byte age;
Object addr;
}
}
默认打印结果:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 0 int [I.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
com.jvm.JolExample$Student object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 63 cc 00 f8 (01100011 11001100 00000000 11111000) (-134165405)
12 4 int Student.id 0
16 1 byte Student.age 0
17 3 (alignment/padding gap)
20 4 java.lang.String Student.name null
24 4 java.lang.Object Student.addr null
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
Process finished with exit code 0
之后的结果打印:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 1c 1a 1c (00000000 00011100 00011010 00011100) (471473152)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 68 0b 1a 1c (01101000 00001011 00011010 00011100) (471468904)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
20 4 (alignment/padding gap)
24 0 int [I.<elements> N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
com.jvm.JolExample$Student object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) f8 cd 83 1c (11111000 11001101 10000011 00011100) (478399992)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 4 int Student.id 0
20 1 byte Student.age 0
21 3 (alignment/padding gap)
24 8 java.lang.String Student.name null
32 8 java.lang.Object Student.addr null
Instance size: 40 bytes
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
A、Object对象头分析
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Mark Word标记在64位机器占8个字节,所有前两行为Mark Word标记数据,第三行为Klass Pointer类型指针占4个指针(开启了指针压缩,不开启占用8字节),第四行为对象的对齐(有时候有,有时候没有),为了保证对象是8个字节的整数倍,所以在12个字节的基础上补了4个字节,达到16个字节。
B、数组对象头分析
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 0 int [I.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Mark Word标记在64位机器占8个字节,所有前两行为Mark Word标记数据,第三行为Klass Pointer类型指针占4个指针(开启了指针压缩,不开启占用8字节),第四行为数组长度占四个字节,由于16字节是8的整数倍,没有对象对齐。
C、Student实例对象头分析
com.jvm.JolExample$Student object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 63 cc 00 f8 (01100011 11001100 00000000 11111000) (-134165405)
12 4 int Student.id 0
16 1 byte Student.age 0
17 3 (alignment/padding gap)
20 4 java.lang.String Student.name null
24 4 java.lang.Object Student.addr null
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
Mark Word标记在64位机器占8个字节,所有前两行为Mark Word标记数据,第三行为Klass Pointer类型指针占4个指针(开启了指针压缩,不开启占用8字节),第四行为int类型的ID字段占4个字节,第五行为byte类型的age字段占一个字节,内部进行对齐补了3个字节,第六行为String类型的name字段占4个字节,第七行为Object对象指针占四个字节(开启指针压缩占4个字节,不开启占8个字节),最后一行对象对齐补了4字节,整个对象大小为32字节。
(4)对象的指针压缩
- JDK1.6 Update14开始,在64位操作系统中JVM默认支持指针压缩
- JVM配置参数:UseCompressedOops,含义为:compressed——压缩、oop(ordinary object pointer)——对象指针
为什么需要进行指针压缩?
- 在64位平台的HotSpot中使用32位指针(实际存储用64位),内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力。
- 为了减少64位平台下内存的消耗,启用指针压缩功能
- 在JVM中,32位地址最大支持4G内存(2的32次方),可以通过对对象指针的存入堆内存时压缩编码、取出到cpu寄存器后解码方式进行优化(对象指针在堆中是32位,在寄存器中是35位,2的35次方=32G),使得JVM只用32位地址就可以支持更大的内存配置(小于等于32G)
- 堆内存小于4G时,不需要启用指针压缩,JVM会直接去除高32位地址,即使用低虚拟地址空间 堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内存不要大于32G为好
- :对于大部分处理器,对象来对齐填充都是。
5、执行方法
执行方法,即对象按照程序员的意愿进行初始化。对应到语言层面上讲,就是为属性赋值(注意,这与上面的初始化零值不同,这是由程序员赋的值),和执行构造方法。
public class Student {
public static final int initData = 666;
public int exam() {
//一个方法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 2;
return c;
}
public static void main(String[] args) {
Student student = new Student();
student.exam();
}
}
javap -v查看字节码文件:
二、对象内存分配
1、对象栈上分配
从JVM内存分配可知JAVA中的对象都是在堆上进行分配,当对象没有被引用的时候,需要依靠GC进行回收内存,如果对象数量较多的时候,会给GC带来较大压力,也间接影响了应用的性能。为了减少临时对象在堆内分配的数量,JVM通过确定该对象不会被外部访问。如果不会逃逸可以将该对象在内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力,也就是说:。
对象逃逸分析的含义
就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,说明这个参数逃逸了。
例如下面这个代码案例,可以看出saveAndReturn方法中的user对象被返回,因此这个对象的作用域范围不确定;saveNoReturn方法中的user对象我们可以确定当方法结束这个对象就可以认为是无效对象了,对于这样的对象我们其实可以将其分配在栈内存里,让其在方法结束时跟随栈内存一起被回收掉。
//User对象可逃逸 public User saveAndReturn() { User user = new User(); user.setId(10); user.setName("张三"); //save to db return user; } //User对象未逃逸 public void saveNoReturn() { User user = new User(); user.setId(