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。

posted on 2024-06-27 00:42  丶柚子  阅读(2)  评论(0编辑  收藏  举报

导航