JVM(二)Class类的加载过程

本文详细解析了Java类文件如何从硬盘加载到内存,涉及类加载器、加载步骤、双亲委派机制,以及自定义加载器的实现。重点讲解了类加载的三个阶段:加载、链接和初始化,以及类加载器层次结构和懒加载策略。

Class Loading Linking Initalizing

有一个class文件,它默默躺在硬盘上,需要经过一个什么样的过程才能到内存里准备好呢?
在这里插入图片描述

class怎么进入内存分为三大步:

  1. 第一步Loading
    把一个class文件通过ClassLoader读取到内存,他本来是一个class文件上的一个一个的二进制,一个一个字节,装完之后就是接下来的Linking
  2. 第二步Linking,分为三小步:
    • Verification
      是校验装进来的class文件是不是符合class文件的标准,假如你装进来的不是这个CA FA BA BE,在这个步骤就被拒接掉了
    • Preparation
      是把class文件静态变量赋默认值,不是初始值,比如你static int i =8,注意在这个步骤8并不是在把i值赋值成8,而是先赋值为0
    • Resolution
      把class文件常量池的符号引用转换成直接引用(内存地址)
  3. 第三步Initlalizing
    调用类初始化代码,给静态成员变量赋初始值
  • 假如有一个成员变量private int m = 8;要想用起来的话是需要new 对象的,这点比较简单,这个new对象的过程分为2步,第一步是给这个对象申请内存的过程,申请完内存之后它里面的变量先赋默认值.new T再来分解的话他实际上中间也是两步,第一步是new出来T这个内存,new出来之后里面这个成员变量还没赋值呢,他默认值为0,下一步他才会调用构造方法,调用构造方法之后才会赋初始值变成8.
    load-默认值-初始值
    new-申请内存-默认值-初始值

类的加载器

在这里插入图片描述

  • 我们来看这个类的加载器的内容,首先第一点JVM它本身有一个类加载器的层次,这个类的加载器本身就是一个普通的class,jvm有一个类加载器的层次分别来加载不同的class,jvm索引的class都是被类加载器加载到内存的,那么这个类加载器可以叫做ClassLoader.
  • 每一个class在java虚拟机里面到内存里面任何一个class都是被ClassLoader load内存的,那么这个ClassLoader其实就是顶级有一个父类,这个父类就叫ClassLoader,他是一个abstract抽象类,相当于这个类是被谁领到内存里去了,他一定是ClassLoader这个类的子类,如果你想知道你的class是被谁弄到内存里的,其实很简单就是下面代码
    System.out.println(类名.class.getClassLoader)
  • 一个class文件平时躺在硬盘上,这个内存被load到内存之后,创建了两块内容,第一块内容把class二进制扔到了内存中,第二块内容是于此生成一个class类对象,这一块对象指向第一块内容
  • 以前用过反射的同学应该知道,我们通过class这个对象去拿他的有哪些方法甚至方法可以调用,分析一下执行反射的过程中,他一定是那些方法的信息存在这个对象里,然后真让这个方法执行的时候,他一定会去找那些class文件里面的二进制码,翻译成java指令一步一步来执行
  • 类加载器层次
    类加载器加载过程,加载过程分为不同的层次来加载,不同类的加载器来加载不同的class文件
    在这里插入图片描述
  1. 第一个类加载器层次
    最顶层的是BootStrap(c语言实现),他是用来加载lib里jdk最核心的内容,比如说rt.jar,charset.jar等核心类,当我们调用getClassLoader获取到null值的时候,代表已经达到了最顶层的加载器
  2. 第二个类加载器层次
    这个是Extension加载器扩展类,加载扩展包里各种各样的文件,这些扩展包在jdk安装目录jre/lib/ext下的jar
  3. 第三个类加载器层次
    这个就是平时用的加载器application,它用于加载classpath指定的内容
  4. 第四类的加载器层次
    这个就是自定义加载器ClassLoader,加载自定义的加载器
    CustomClassLoader父类加载器>application父类加载器>Extension父类加载器>Bootstrap
    在这里插入图片描述
    注意:这个图讲的是这个classLoader语法上是从谁继承的,不是类加载器的继承关系
  • 双亲委派
    在这里插入图片描述

一个class文件需要被load内存时候是这样的
任何一个class,加入自定义classloader,这时候先去自定义的classloader里面找,他内部维护着缓存,看缓存中有没有,有就直接返回,没有的话并不会直接加载,而是就接着往上级找,有就返回,没有就继续往上级找,最后如果父类Bootstrap也没有加载过,就委托Extension去加载,如果能加载就加载,不能加载就往下传递,如果能就就直接加载,最后如果都不能加载,就抛出异常ClassNotFound找不到,这就叫双亲委派机制
在这里插入图片描述

为什么要搞双亲委派机制?
主要是为了安全,加入用反正法,如果任何一个class都可以把它load到内存的胡,那我就可以给你java.lang.string,我交给自定义classloader,把这个string load到内存,打包给客户,然后给密码存储成string对象,我可以偷偷摸摸的把密码发给我自己,那就不安全了
双亲委派机制就不会出现这样的情况,自定义classloader加载一个java.lang.string他就产生了警惕,他先去上面查看有没有加载过,上面有加载过,直接返回给你,不给你重新加载

父加载器不是"类加载器的加载器"也不是"类加载器的父类加载器"
Laucher就是ClassLoader的一个包装类启动类,在这个类里面你要去看源码,就会看出很多内容,比如:类的加载路径,其实这些都规定在Launcher源码里面了
你可以通过System.getProperties(“类的加载路径(上面类加载路径图地址)”)来获取对应类加载器能加载的jar包,可以加一个replace()方法把;符号;换成System.lineSeparator()
双亲委派主要的含义就是当你要求一个classloader去load class的时候,它首先去找他的父亲,注意这个父亲不是继承关系上的父亲,是通过他的成员变量parent,是源码指定的被final修饰了,自定义的classLoader默认是通过父类的无参构造方法指定的,你可以调用父类的有参构造来设置自定义classLoader的父加载器

  • 自定义加载器
    在这里插入图片描述
    需要先来看一下源码:
    在这里插入图片描述
    loadclass这个方法过程,就说你要加载一个类你只需要调用classLoader的loadclass方法就能够把这个类加载到内存中,加载到内存他会给你返回一个class类的对象,也就是刚才的过程,在硬盘上找到这个类的源码,这些源码可能在那些目录下面,找到这个class文件源码之后,把他load到内存,于此同时生成一个class类对象,把这个class对象返回给你
    什么时候我们需要自己去加载一个类?Tomcat在load自己的那部分肯定是要load自定义的那些class的JRebel热部署你这个class放硬盘上我怎么去给你热替换,肯定需要一个classloader手动的load到内存里面去,spring动态代理,他是一个新的class,当你要用的时候他把新的load内存里了

在这里插入图片描述
自定义类加载器:
ClassLoader源码执行步骤:
findInCache->parent.loadClass->findClass()
自定义类加载器很简单,需要你自己继承ClassLoader类,去重写findClass方法,在findclass方法里找到需要load进来的二进制的内容,在内存里给它完整的load进来,load完之后再把这部分内容转换成class类对象,用defindClass方法,这种模式叫钩子函数模板方法
lazyLoading
懒加载
在这里插入图片描述
java是懒加载,什么时候需要才加载
执行模式
一个class文件load到内存之后通过java的解释器intepreter来执行,JIT的编译器指的是有某些代码我需要就把它编译成本地代码,相当于是exe,所以当有人问你java是编译语言还是解释语言,你完全可以告诉他我想解释就解释想编译就编译看你怎么定了,默认是混合模式,就是混合使用解释器+热点代码编译(hotspot)
什么叫做热点代码编译?
多次被调用的方法,多次被调用的循环,进行编译,怎么检测呢?就是用一个计数器,每个方法上都有一个方法计数器,循环有循环计数器,结果在发现某个方法执行了超过某个设定的值(XX: CompileThreshold=值),就要对它就行编译,直接编译成本地代码,在用的话直接用本地的,不要解释器执行了
为什么不直接都编译成本地代码呢,执行效率更高?

  1. 因为java解释器现在效率也非常高了,在一些简单的代码执行上它不输于编译器
  2. 如果你有一段代码执行文件特别特别多各种各样的类库时候,好几十个class这是很正常的,你上来二话不说先给编译器编译,过程会非常的长,所以现在默认的模式是混合模式,但是完全可以用参数来指定是什么模式
    在这里插入图片描述
    扩展知识:
    如何打破双亲委派机制?
    1. 重写loadClass()
    2. 在JDK1.2之前,自定义ClassLoader都必须重写loadClass()
    3. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
    4. 模块化的时候使用热部署
      比如:tomcat,都有自己的模块指定classloader(可以加在同一类库的不同版本),两个classloader可以load进来同名的类,这是完全没有问题的,这里已经打破了双亲委派机制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值