JVM类加载机制

1、JVM跨平台原理

所有的操作系统都运行了一个JVM,JVM都可以去执行字节码文件。把对应的Java代码编译成字节码文件之后,JVM就能执行了

有的代码是编译型的,有的是解释型的,但是Java是二合一。生成字节码文件是在编译的过程,而不是运行的过程

那为什么不直接让JVM执行.java后缀的文件呢?如果想这么做的话,就像解释型语言一样,JVM内部需要一个解释器,会导致Java虚拟机速度变慢,JVM看懂需要一些时间。还有一个原因是因为,JVM设计的初衷还有一个就是让其他语言也运行到JVM上,所以如果是其它语言编写的代码,只要经过编译可以生成.class的字节码文件,那么这种语言的程序就可以运行在Java虚拟机上,比如说Kotlin、Clojure、Groovy、Scala等,那么如果只能处理.java文件就意味着只能处理Java一种语言,没有达到Java虚拟机设计的初衷。

2、JVM整体结构

1、类加载子系统:字节码文件是在磁盘中的,要运行的话需要把东西放在内存中,那么使用类加载子系统可以把字节码文件调进内存去

然后字节码指令是放在方法区的,机器想要执行指令的话,还需要把字节码指令解释成机器指令,这里执行引擎中提供一个东西叫做JIT编译器,它的作用是将一些热点指令(经常执行的)缓存起来,这样,当一条指令被多次调用的时候就不用每次都解释了。

2、执行过程中生成的一些对象就放在堆中

3、我们执行指令就相当于执行的是Java代码中的一个一个方法,这些方法的局部变量以及相关信息会放在Java方法栈中,也叫虚拟机栈

4、本地方法栈:这个地方存放的是本地方法的一些相关信息,本地方法就是Java代码调用其他语言编写代码的接口

5、程序计数器:执行指令的时候,进行线程切换的时候,当线程且回来的时候,我们需要知道要执行哪条语句了。所以这个程序计数器的作用就是记录要执行的下一条语句的地址。

3、类加载子系统

上面说了,类加载子系统的作用就是把class文件加载到内存的方法区中。

在这个过程中,首先就是加载,从磁盘中调出来。然后就是链接:链接分为三个步骤,分别是验证(验证文件格式是否正确)、准备(给static类型变量分配内存并赋零值)、解析(将符号引用解析为直接引用,如果你在一个类中用到了另外的类,是通过包名这样一个字符串类型进行引用的,在解析的时候,会把引用的类在内存中地址返回,也就是将符号引用转换为直接引用)。

然后在初始化阶段,我们将准备阶段没有赋值的static变量赋值

4、类加载器

两种,自定义类加载器,引导类加载器

引导类加载器(BootStrapClassLoader)加载的是jre/lib目录下的类,ExtClassLoader加载的是jre/lib/ext包下的类。而AppClassLoader加载的是当前项目的目录的target文件下的.class后缀的文件。

(详细的在下边说,这里先有个印象)

5、双亲委派

如果我们想实现一个自定义的类加载器,那么这个类加载器需要继承于ClassLoader抽象类,并且重写它的loadClass方法,那么就是做了自定义类加载。如果不做自定义类加载器去加载类的话,走的就是JVM默认加载某一个类的逻辑:

1、当一个类被加载的时候,把类名传入,首先判断这个类名的类是否被加载过,如果被加载过的话直接返回Class类型的对象,如果没有被加载过:

2、去判断当前加载器(AppClassLoader)的parent是不是空,如果不为空,调用parent的loadClass方法,如果为空的话,就去调用BootStrapClassLoader去加载得到(往上找)

3、如果BootStrapClassLoader没加载到,就返回让ExtClassLoader去加载,如果Ext也没加载到,就让AppClassLoader去自己的目录(也就是当前项目的目录去找类并加载)找到类并加载

双亲委派的作用:防止类的重复加载。防止一些核心的API被篡改

1.防止一些核心的API被篡改:比如说如果有黑客,在你的项目里边写了一个String类,想让你的所有字符串都用的是黑客写的,但是由于双亲委派,所以加载到的先是上层的,也就是父类里边的,BootStrapClassLoader去加载的是jre/lib下的String,那么黑客定义的这个String就不生效

2.防止类被重复加载:如果一个类已经被加载过了就会把信息存放在ClassLoader中,直接返回,这样就不会重复了

6、Tomcat为什么要自定义类加载器

我们在做类加载的时候,都用的是类加载器的实例对象。所以我们判断一个类是否被加载过只是判断当前的类有没有被这个类加载器的实例对象加载过。

在Tomcat中,如果两个应用里面有两个类是同名的,比如说都叫HelloServlet,那么A应用中的HelloServlet被加载之后,B中的HelloServlet就不会被加载了,所以Tomcat要自定义类加载器,避免出现刚刚说的那种情况。有几个应用就创建几个实例化对象,使用单独的类加载器,也就是WebappClassLoader。目的就是为了实现类加载的隔离。

JavaGuide的类加载

1、类加载过程

  • 类加载过程分为三步:加载 连接 初始化
  • 连接过程又分为三步:验证 准备 解析

2、类加载器

类加载器是一个负责加载类的对象,ClassLoader是一个抽象类。给定类的二进制名称,类加载器将类从硬盘调入JVM管理的内存中。类加载是一个负责加载类的对象,用于实现类加载过程中的加载这一步。类加载器的主要作用就是加载Java类的字节码文件到JMM中(在内存中生成一个代表该类的Class对象)

3、类加载器的加载规则

JVM启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说大部分类在具体用到的时候才会去加载,这样对内存更加友好。

但是对于已经加载过的类会放在ClassLoader中,类加载的时候系统会首先判断该类是否被加载过,加载过直接返回,没加载过再加载。对于一个类加载器来说,相同二进制名称的类只会被加载一次

4、JVM内置的三个重要ClassLoader

​ 1、BootStrapClassLoader

​ 启动类加载器,最顶层的加载类,主要用来加载JDK内部的核心类库

​ 2、ExtensionClassLoader

​ 扩展类加载器,主要负责加载jre/lib/ext目录下的jar包和类

​ 3、AppClassLoader

​ 应用程序类加载器,面向用户的加载器,负责加载当前应用classpath下的所有jar包和类


对于这些类加载器,我们自底向上查找判断类是否被加载,自顶向下尝试加载类

  • 每个类加载器(ClassLoader)可以通过getParent得到其父ClassLoader,如果获取到ClassLoader为null的话,那么该类就通过BootSharpClassLoader加载

5、自定义类加载器

自定义类加载器需要继承ClassLoader抽象类,该类中有两个关键的方法,分别是loadClassfindClass

protected Class loadClass(String name, boolean resolve):加载指定名称的类,实现双亲委派机制

protected Class findClass(String name):根据类的二进制名称来查找类,默认为空

在默认的loadClass方法中,会有一个parent.loadClass() ,如果想要打破双亲委派,就重写loadClass,如果不想打破双亲委派,就只重写findClass

注意:类加载器之间的父子关系一般不是通过继承来实现的,而试图用尝试用组合关系来复用父加载器的代码。组合就是在子加载器中定义一个ClassLoader类型的对象,在构造器里边去构造对应的parent

6、双亲委派的执行流程

每当一个类加载器接收端哦加载请求的时候,它会先将请求转发给父加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载

1、首先判断当前类是否被加载过,已经被加载过的类会直接返回,否则回尝试加载(每个父类加载器都会判断一下有没有加载过)

2、类加载器进行类加载的时候,它首先不会尝试加载这个类,二十八这个请求委托给父类加载器去完成。这样的话,所有的请求最终都会传送到顶层的启动类加载器中(BootStrapClassLoader)

3、只有当父加载器反馈自己无法完成这个加载请求的时候,子加载器才会尝试自己去加载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值