类的加载机制和类加载器,马士兵教育公开课笔记

什么是类加载器?

类加载器是JVM的一部分,也是java文件从编译到执行过程中重要的一部分

首先,java编译器javac会编译java代码,将代码编译为.class文件。之后.class文件被Classloader load到内存上。同时,Classloader还会同时将java所需要的类库也load到内存。之后让字节码解释器来解释.class文件,最后执行引擎将程序的执行依托于OS硬件上。

类加载和初始化的执行过程

懒加载

JVM采用了懒加载机制,也就是:我用到哪个,就去加载哪个

避免了大型类库中的类并没有被使用但是被加载,浪费了资源。

一个类需要被用到的时候,就会被load到内存。

初始化和加载其实是同一个概念。一般情况下我们通过new来实例化一个类,这个类被实例化的时候,就会被加载到内存。但是,如果我们不去实例化一个对象,这个类也有可能被加载到内存。

加载过程

  1. Loading

将.class文件从硬盘load到内存,并生成一个Class对象,指向它的.class文件

  1. Linking
  1. 对文件进行校验,看其符不符合.class文件的标准。
  2. 为static变量赋予默认值(例如:static i = 8 在此处 i = 0)
  3. 初始化常量池中的引用,从符号引用改变为地址引用内容。
  1. Initialing

将static变量赋予初始值,并执行static代码块。

JDK1.8后生成的Class对象存放在堆中。Class类中存在很多很多方法,可以获取类中的方法,变量等信息。这些是通过在.class文件中操作得到的。类名.class = 类名.getClass()

类被加载的情况

不会初始化子类的情况,称之为被动引用:

1.只调用了父类的static,final变量

2.创建类的数组并不会引发类的初始化

3.调用了常量。常量即使我们实例化了类,它也不会变,所以直接就生成好放在内存就行了。所以我们不需要去加载一个类也能知道它内部的常量的值。

虚拟机规范定义了五种会立即初始化类的情况:

1.使用new关键字实例化对象时、读取或设置一个类的静态字段的时候以及已经调用一个类的静态方法的时候,调用final不会引起类的初始化。

2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要先触发其初始化。

3.当初始化一个类的时候,如果发现其父类没有初始化,就会先初始化它的父类。

4.当虚拟机启动的时候,用户需要指定一个要执行的主类(就是包含main()方法的那个类),虚拟机会先初始化这个类

5.使用JDK1.7动态语言支持的时候的一些情况

注意:

B类的父类A中如果存在一个常量对象 static final Integer a = new Integer(7);那么调用B.a会引起A类的初始化,将Integer换成String也同理。

而A类中如果存在的是常量 static final int a = 7; 那么调用B.a不会触发A类的初始化。

class文件在Java中的运行模式

 众所周知,Java是解释执行的语言。一个.class文件被load到内存后,通过解释器(bytecode interpreter)解释执行。但实际上Java还存在一个编译器:JIT(Just In-Time compiler)

JIT会将短时间内执行多次的方法次被调用的循环编译为本地代码块(类似.exe格式),以提高执行速度。

Java默认采用了混合模式,也就是解释器和编译器共同执行一个.class文件,并通过方法计数器和循环计数器监视某些方法和循环,并对使用次数较多的方法和循环编译成本地代码。来提高程序运行速率。

解释器执行:执行很慢,启动很快。

编译器执行:启动很慢,因为需要编译代码为本地代码,执行极快。

Java有三种执行模式:

  1. 混合模式:解释器,编译器一起工作,速度比较均衡。
  2. 纯解释模式:只有解释器工作,启动速度奇快,但是执行速度像蜗牛爬行。
  3. 纯编译模式:只有编译器工作,启动速度很慢很慢,但是执行超快。

执行模式可以通过命令行参数设置,-Xmixed为混合模式,是默认值。-Xint为解释模式。-Xcomp为编译模式。

类加载器

不同的类由不同的加载器load到内存,类加载器主要分为四个层次:

  1. 最高层级:Bootstrap : 所有jdk的核心类库,比如String,Object.... 都由它来加载,内部由C++实现,所以在调用java.lang.String.class.getClassLoader返回的是null。Java并不能正确地找到它。
  2. Extension(ExtClassLoader):jre/lib/ext文件夹下的所有类由它来加载。
  3. App(Application,AppClassLoader):所有CLASSPATH下的类由它来加载
  4. CustomerClassLoader:用户自定义类加载器,用来加载用户指定的类。

实际上类加载器在Java内部也是一个类。所有的类加载器都是由顶层的Bootstrap来load到内存当中的,之后,它们再去load其他类。

值得一提的是,类加载器的层次并不是它们的父子关系,也就是说Extension的父类并不是Bootstrap,App的父类并不是Extension。所有ClassLoader的顶级父类是ClassLoader类。类加载的子父类关系如下所示:

 通过类名.class.getClassLoader()获得到的类加载器格式如下:

 正常的类显示应为:类名+HashCode码。其中$的意思是:ExtClassLoader是sun.misc.Launcher类中的一个内部类。中间用$标注。

类加载器的双亲委派机制

一个类被load到内存的过程采用了双亲委派机制。整个ClassLoader的层次会被遍历两遍

 加入当一个类:S 需要load到内存时,首先会从CustomClassLoader开始询问当前加载器是否已经加载了S类,类加载器会在自己内部的容器中寻找是否有S,如果有,则返回。如果没有则向上一层询问App是否加载了这个类。以此类推到Bootstrap,如果依旧没有返回,则Bootstrap开始尝试加载这个类,如果不归自己加载,则指派下一层的Ext去加载这个类,如果依旧不归Ext管,则再由Ext指派App。以此类推到CustomClassLoader,如果都不能load这个类,则抛出ClassNotFoundException。

以上过程就是双亲委派。先由底向上询问是否已经初始化,如果没有则从高向下指派加载器去加载类。

为什么使用双亲委派机制?

1. 安全

这是最关键的点。在load类的过程中,如果我们自己定义了一个java.lang.String,想去覆盖JDK核心类库中的String并在其中添加具有攻击性的代码,如果我们规定一个自定义加载器去加载,那么就会产生严重的问题。所有使用了这个类的用户都会被影响。所以自定义加载器会先向上询问是否已经加载,到Bootstrap时,它会返回已经存在的真正JDK核心类库中的java.lang.String,防止出现安全问题。双亲委派过程是写死的,即使自定义了类加载器可以加载,也会先经过双亲委派机制。

2. 防止资源浪费

对于一个类load到内存应该是单例的,即每个类只应该拥有一个自己的Class类。所以每次先询问的过程就可以防止一个类被多次load到内存中,浪费资源。

类加载器的范围(加载的路径)

Bootstrap:

 ExtClassLoader

 AppClassLoader

 部分jre/lib和jre/ext目录下的jar以及ClassPath目录下的jar

自定义类加载器

设定一个类,继承ClassLoader,并且重写其中的findClass方法

 在ClassLoader的源码中,load的过程会调用loadClass方法,该方法中严格定义了双亲委派过程。 

 通过解析源码,最终发现在双亲委派的最后一步,会调用findClass方法,这也就是我们的自定义类加载器需要实现的。

自定义类加载器实现的实例

  1. findClass方法中首先创建一个File对象指定.class文件路径,创建文件流和字节数组流。
  2. 利用文件流将.class中的内容读出来并存储在字节数组流中。
  3. 将字节数组流转化为字节数组
  4. return defineClass(name , bytes , 0 , bytes.length) 四个参数分别为:文件名,字节数组,字节数组起始位置,字节数组结束位置。该方法是ClassLoader中已有的。

通过自定义类加载器实现加密功能

对于一般的.class文件,java都有一套标准。比如开头的格式以及其他的一些内容。所以,.class文件被反编译实际上相当容易。当我们不想让别人对我们的.class进行反编译的时候,我们可以先对.class文件进行加密,再通过自定义类加载器实现解密读取,最后再把正确的load到内存就OK了。

实例

首先定义一个种子seed,用于异或加密

1.通过该方法对整个.class文件的每个字节进行异或加密

2.通过在findClass方法中在读字节时继续异或seed达到解密的目的,这样字节数组里的内容就是正常.class文件的内容了。

3.把正确的.class文件内容load到内存。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值