java类加载器
参考(https://blog.csdn.net/Promise_J_Z/article/details/121632656)
Java默认的类加载器分为三种
1.bootstrat ClassLoader 加载lib目录下的jar包 最底层的类加载器
2.extention classLoader 加载lib/ext目录下的jar包 第二底层的类加载器
3.app(application) classLoader 加载classpath目录下的jar包
4.自定义类加载器 自定义类加载器就可以自定义加载目录下的jar文件 实现方式:继承applicationLoader 然后重写findClass方法即可
为了避免类加载器重复加载jar包 一定要注意遵从双亲委派原则(即优先判断bootstrat是否加载了该类,如果没有在判断extLoader是否加载了该类。。。。(如果自定义了类加载器则还需要判断 applicationLoader 嵌套循环))
自定义类加载器使用场景:
1.加载非classpath路径下(自定义路径)的类。
2.希望解耦,常用在框架设计。
3.这些类希望予以隔离,不同工程应用下的同名类都可以加载,不冲突,常见tomcat.
步骤:
1.继承classLoader父类。
2.遵从双亲委派原则,重写findClass方法。
3.不要重写loadClass方法,否则不会走双亲委派。
4.读取要加载类的字节码文件。
5.使用父类的defineClass方法。
6.要加载的类使用自定义类加载器来加载。
举例:
自定义类加载器
myClassLoader extends appliactionLoader
findClass(String name) {
String path = "F:\\" + name + ".class";
ByteArrayFileOutputStream out = new ByteArrayFileOutputStream();
Files.copy(Paths.get(path), out);
bytes[] bytes = os.toByteArray();
return this.defineClass(name, byte, 0 byte.length);
}
myClassLoader .findClass("a") 加载A.class
加载类:
类被加载后,类的静态常量,属性,方法,父类,类加载器,虚拟方法等都会在元空间中存储。
虚拟机会在堆中创建一个类对象和一个instanceklass(存有指向元空间中的地址和所有该类实例的地址)
创建该类的多个实例对象,每个实例对象的对象头中都存有类对象的堆地址,这样类对象和类的实例们就关联起来了。
类对象在堆中的存储结构:对象头,实例数据,对齐填充物。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
对象创建的主流程
类加载检查------》加载类-----》分配空间----》初始化零值-----》设置对象头----》执行init方法
1类加载检查 判断类是否已加载,如果已加载则直接进入分配空间阶段,如果没有加载则调用加载方法加载(类加载器的loadClass()方法)。
2加载了类就能得到类信息,知道需要分配多少空间。
3分配空间:2种方式
3.1指针碰撞:如果堆中内存使用是规整的,已使用的内存在一边,未使用的内存在另一边,指针只需要在边界摆动相应内存大小的幅度即可。(默认方式)
3.2空闲列表:如果堆中内存使用不是规整的,已使用内存和未使用内存混在一起,则虚拟机需要创建一个列表来记录哪些空间已被使用,哪些空间未被使用。
4初始化零值:分配空间后,虚拟机将分配空间的值都设置为零(不包括对象头),这样Java类未实例对象也能获得各属性的零值对应的初始值。比如int类型的零值就是0,String类型的零值就是空值,Object的零值是null.
5对象头: 虚拟机初始化零值后就要对对象的进行必要的设置,比如对象的哈希码,对象的GC分代年龄,锁标志状态,线程ID等。
每一个类被加载就会设置对象头,后续所有对应类的实例的内存地址也都会被放在对象头下。
类似于:A类
A类对象头 XXXXXXXXX(地址)
A1实例 XXXXXXXXX(a1对象在堆中的地址)
A2实例 XXXXXXXXX(A2对象在堆中的地址)
6执行程序员设置的init方法。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
问题:类加载器创建对象和new声明创建对象有什么区别。
类加载器创建对象是调用堆中类对象的默认无参构造方法。如果类声明中定义了有参构造方法,使用该方式创建对象就会抛出异常,除非再定义一个无参构造函数。 如果类声明中没有定义构造方法就会自动创建无参构造方法。也就是说类加载器调用newInstance()方法一定会调用无参构造函数,而NEW声明的方式可以使用指定的构造函数创建对象。
new声明创建对象是调用指定的构造方法创建对象。
这个TestInstance类我定义了一个带参数的构造方法 调用newInstance()失败抛出异常
现在删除带参数的构造方法 创建成功
既有带参构造函数也有无参构造函数 创建成功
new完一个对象的存储情况。
在方法中new一个对象,对象的数据等信息是在堆中(假设地址为X),对象在栈中存储的是一个引用地址指向X。而且X还会被存到堆中类对象的instanceklass中。
举例:A类创建一个对象A1
则堆中:
A.class + instanceKlass(包含A1在堆中的地址X和A.class在元数据中的地址(存储类的fields,methods,super等信息)); A1存储地址X+A1的对象头存有A.CLASS的地址
栈:
一个引用地址指向X。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)