new一个对象会发生的事(JVM层面)
前言谈谈类加载机制
类的加载过程,分为加载,连接和初始化,而连接又分为三个过程,验证准备和解析
其中加载部分,简而言之,通过类加载器,将.java文件加载为.class文件存入JVM内存
连接部分主要是对.class文件在运行器前做一些修饰性的工作
- 验证部分:保证.class文件的内容符合虚拟机要求的规范,不会存在安全性问题,例如是否修改了final方法,是否引用了看不见的函数(private),是否存在不合理的类型转换等等
- 准备部分:这个部分主要是对.class文件的类变量(注意不是实例变量,即static变量)做一个初始化归零操作,并且分配内存,注意这里的初始化和后面的初始化不同,这里是将所有变量根据不同的变量属性而赋予不同的值,例如int为0,string为null,不会赋予类声明时的初始值,但是例如final static int a=456,这个时候a的值还是为456,总而言之:
- 常量型变量的初始值为声明时的值(例如a)
- 引用型则初始为null
- 8种基本类型则默认为0
- 解析部分:这个部分是对class文件中的符号引用替换为直接引用。
- 符号引用则为一个符号,可以唯一标识一个函数。
- 直接引用则为该函数的地址
例如,一个函数a(),其地址为1234(函数在方法区中的偏移量),此时的符号引用为a,而直接引用为1234,当我们在java代码中调用函数a,经过这一步之后就会直接替换为1234
最后一个初始化部分:初始化部分即将类中的类变量赋值(非final的static变量),如果父类没有初始化过,那么就先初始化父类。
以上即为简单的类加载过程的介绍。
eg:类加载的时机:
- new,或者调用某个class的静态变量,函数
- 反射时
- 如果加载一个类时,其父类还没有加载,那么会去先加载他的父类
- jvm启动的时候会去先加载一个子类
- 动态代理(其实就是反射)
注意:以下并不会加载类:
- 通过子类来引用父类的静态变量,不会加载子类
- 引用一个类的静态常量
- 创建一个该类的数组,因为其实现在底层还是object
然后就是new对象的过程
- 第一步就是先去常量池是否能够定位到这个类的引用,如果找到再看看这个类是否已经加载过了,如果此时对应的类没有加载,会去先加载这个类,加载的过程参考上面的过程。
- 如果此时已经加载过,那么这个类对应的对象大小其实已经知道,再去堆区中划分内存即可。这里划分内存其实就有几种方法,主要是根据垃圾收集器的不同而不同
- 当垃圾收集器使用“标记-清除”时,那么就应该使用空闲列表法,存储哪些块是可用的(因为这个时候内存会分为很多快,零碎)
- 而当垃圾收集器使用“标记整理”或者“标记复制时”,那么就会使用指针碰撞,这个主要就是因为内存是连续的,不会产生内碎片,可以直接根据已经用的内存尾部直接开始划分一块内存区域。
- 注意:这里内存分配其实还要考虑多线程的问题,有两种解决方法,第一种是加CAS锁,分配失败就再次分配,第二种是每个线程先去拿一些可以用的内存空间放在自己的缓冲池中(TLAB),等到实际用的时候就直接分配,用完了再去申请(类似于我们之前生成分布式ID的时候使用的方法),这样就解决了多线程的问题。
- 对象内存分配空间的清零操作(java对象主要由对象头,示例数据和对其填充组成,其中对象头不会被清零)
- 虚拟机对该对象做一些数据的复制(主要是对象头,例如GC年龄,分代信息,锁标志位,hashcode之类的)
- 执行对象的Init()初始化方法,给对象的属性赋值
- 将该对象的地址返回给栈中对应调用的引用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律