JVM——Java类加载机制总结

1. 类加载器的组织结构

转载请注明出处:JVM——Java类加载机制总结_SEU_Calvin的博客-CSDN博客

类加载器 ClassLoader是具有层次结构的,也就是父子关系。其中,Bootstrap是所有类加载器的父亲

(1)Bootstrapclass loader 启动类加载器

当运行Java虚拟机时,这个类加载器被创建,它负责加载虚拟机的核心类库,如java.lang.*等。

(2)Extensionclass loader:标准扩展类加载器

用于加载除了基本 API之外的一些拓展类

(3)AppClassLoader加载应用程序和程序员自定义的类

运行下面的程序,结果也显示出来了:

从运行结果可以看出加载器之间的父子关系,ExtClassLoader的父Loader返回了null

原因是BootstrapLoader(启动类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式。

2. 类的加载机制

类被加载到虚拟机内存包括加载、链接、初始化几个阶段。其中链接又细化分为验证、准备、解析。

这里需要注意的是,解析阶段在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的运行时绑定。各个阶段的作用整理如下:

 

2.1 加载阶段

加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以由用户自定义的类加载器完成,开发人员可以通过定义类加载器去控制字节流的获取方式

1通过类的全名产生对应类的二进制数据流

2)将这些二进制数据流转换为方法区的运行时数据结构

3)创建代表这个类的java.lang.Class对象。作为方法区这些数据的访问入口

 

2.2 链接阶段(实现 Java 的动态性的重要一步)

1验证:验证阶段的主要目的是确保class文件字节流的正确性,要验证比如class文件格式规范、这个类是否继承了final类、不能把一个父类对象赋值给子类数据类型等等。

2准备:准备阶段为方法区中的静态变量分配内存空间。并将其赋值为初始值,所有原始类型的值都为0。如float0f int0boolean0、引用类型为null

3解析:解析阶段把符号引用解析为直接引用

符号引用是一个字符串,它唯一标识一个类、一个字段、一个方法等目标。

而直接引用对于类变量、类方法指的是指向方法区的指针,然后对于实例方法、实例对象来说就是偏移量,比如一个实例方法,子类中方法表中的偏移量和父类是一致的,这个偏移量可以确定某个方法的位置。

 

2.3 初始化

到了初始化阶段,才是真正执行用户定义程序代码。在初始化阶段就是执行类构造器方法的过程,工作包括赋值类变量、静态语句块的合并

//定义在静态语句块之后的变量可以赋值,但不能访问
public class Test{
    static{
        i=0;//給变量赋值,可以通过编译
        System.out.print(i);//这句编译器会提示非法向前引用
    }
    static int i=1;
}

初始化过程会被触发的条件汇总:

1)使用new关键字实例化对象、访问一个类的静态字段静态方法的时候。

2对类进行反射调用的时候。

3)当初始化子类时,如果发现其父类还没有进行过初始化,则进行父类的初始化

 

【关于构造器方法拓展知识】(可以不看)

1)类构造器<clinit>()方法与类的构造函数不同,它不需要显式调用父类构造,虚拟机会保证在子类<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此在虚拟机中的第一个执行的<clinit>()方法的类肯定是java.lang.Object

2)由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。

3<clinit>()方法不是必须的,如果一个类中没有静态语句,那么编译器可以不为这个类生成<clinit>()方法。

4接口中不能使用静态语句块,和类不同的是,执行接口的<clinit>()方法不需要先执行父接口<clinit>()方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。

5)虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步,可能会导致阻塞。

3. 类加载的三种方式

1)由 new 关键字创建一个类的实例。

2)调用 Class.forName() 方法,通过反射加载类。

3)调用某个ClassLoader实例的loadClass()方法。

 

三者的区别汇总如下:

1)方法12都是使用的当前类加载器。方法3是用户指定的类加载器加载

2)方法1是静态加载,23是动态加载。对于任意一个类/对象,我们都能够通过反射能够调用这个类的所有属性方法。

(3)对于两种动态加载,如果程序需要类被初始化,就必须使用Class.forName(name)的方式。loadClass只是加载,不会解析更不会初始化所反射的类。常用于做懒加载,提高加载速度,使用的时候再通过.newInstance()真正去初始化类。 Class.forName()加载完毕一个指定类后,会对该类自动进行初始化。

Class.forName(className);
//实际上是调用的是:
Class.forName(className, true, this.getClass().getClassLoader());//第二个参为true即默认类需要初始化,初始化会触发目标对象静态块的执行和静态变量的初始化
ClassLoader.loadClass(className);
//实际上调用的是:
ClassLoader.loadClass(name, false);//第二个参数即默认得到的class还没有进行链接,意味着不进行初始化等系列操作,即静态代码块不会执行

  • 14
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值