前情概要

对象的结构

  • 对象头

    • Mark Word:存储对象自身运行时的数据,如哈希码、GC分代年龄、锁状态标志
    • 类型指针:知道是哪个类的实例,指向Class对象
  • 实例数据:存储Java类中各个类型的字段,包括基本类型和引用类型

  • 对齐填充:无意义,对象分配的内存是 8byte 的整数倍

对象创建的代码

1
Person person = new Person();

执行以上代码的时候,会经历以下几个步骤:

  1. 类加载
  2. 实例化
  3. 初始化
  4. 引用赋值

类加载

如果在方法区的运行时常量池没有找到Person类的符号引用,那么就会进行类加载,类加载器就会把Person.class文件经过三大步后加载到JVM内存(运行时数据区的运行时常量池)。

类加载器

Person类信息一开始保存在一个Person.java文件,经过编译之后生成对应的Person.class文件,类加载器就会把Person.class文件加载进JVM内存,同时在堆内存中生成相应的Class对象。

一般有三种重要的类加载器:

  • 启动类加载器:C++实现,默认类都会被它所加载,避免核心类冲突
  • 扩展类加载器:
  • 应用类加载器:加载classpath路径下的类

自定义类加载器和双亲委派

当有特殊需要,也可以自己实现类加载器,如.class文件不在classpath路径下,或者.class文件被加密了需要特殊处理才能加载进JVM内存,可以通过继承ClassLoader类,重写findClass()即可,如果需要打破双亲委派模型,就重写loadClass()覆写加载类的逻辑(如tomcat就是先用自定义的APP类加载器加载再考虑应用类加载器)。

三大步

  • 加载:把Person.class文件通过二进制字节流加载到JVM内存,同时在堆内存生成对应的Class对象
  • 连接:验证、static变量赋初值、符号引用转换地址引用(也可能在初始化之后才进行,也叫多态动态绑定)
  • 初始化:调用构造函数,执行静态代码块

实例化

以下链接是我之前写的关于实例化的详细过程,本文主要想说的是实例化这里和GC的关系。

https://fuleyou.top/2024/03/02/%E5%AF%B9%E8%B1%A1%E5%AE%9E%E4%BE%8B%E5%8C%96%E8%BF%87%E7%A8%8B/

image-20240415095811035

垃圾回收

详情可参考https://fuleyou.icu/2024/03/25/GC%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/

在给对象分配空间的时候,如上图所示,如果堆空间不足,就会发生GC,当然还有其它情况其他区域也会发生GC,本文重点讨论堆的GC,因为和实例化有关。

哪些垃圾会被回收?

废弃的类、常量,死亡的对象,这里会涉及到一个词语叫做引用,一般有四种引用类型,不同引用类型的对象的回收时机也不同,如何判断死亡对象也是一个问题。

垃圾回收器和垃圾回收算法?

这里的选择会影响新对象的分配方式,一般有指针碰撞(无内存碎片)或者是空闲列表(有内存碎片)。

初始化

  • 初始化零值:给对象的实例字段分配默认值,避免使用的时候出现null
  • 设置对象头:类的元数据信息、hashCode、GC分代年龄、锁状态标志等