资讯详情

JVM对象创建、对象内存分配、对象内存回收机制

系列文章目录

第一节 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堆中分开。

  1. 如何划分内存
  2. 并发, 可能是内存分配给对象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.设置对象头

  1. 初始化零值后,虚拟机需要设置对象,例如对象是什么类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。
  2. 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data)和对齐填充(Padding)。
  3. 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)对象的指针压缩

  1. JDK1.6 Update14开始,在64位操作系统中JVM默认支持指针压缩
  2. 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( 

标签: c08连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台