java破坏器类_吊打面试官-类加载器

1. 什么是类加载器?

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 类的虚拟机使用 Java 方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

2. JVM类加载机制全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

3. 类加载器与类的”相同“判断

类加载器除了用于加载类外,还可用于确定类在Java虚拟机中的唯一性。

即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。

通俗一点来讲,要判断两个类是否“相同”,前提是这两个类必须被同一个类加载器加载,否则这个两个类不“相同”。 这里指的“相同”,包括类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法、instanceof关键字等判断出来的结果。

类加载器种类启动类加载器,Bootstrap ClassLoader,加载JACA_HOME\lib,或者被-Xbootclasspath参数限定的类,该类加载器加载的是核心类库,是由C语言实现的,所以在java代码中获取该类加载器比如String会获取到null,因为它不是java类。

扩展类加载器,Extension ClassLoader,加载\lib\ext,或者被java.ext.dirs系统变量指定的类

应用程序类加载器,Application ClassLoader,加载ClassPath中的类库或者我们自己写的代码。

自定义类加载器,通过继承ClassLoader实现,一般是加载我们的自定义类

按照这个过程可以想到,如果同样在CLASSPATH指定的目录中和自己工作目录中存放相同的class,会优先加载CLASSPATH目录中的文件。

双亲委派模型

总结: 自底向上检查类是否加载,自顶向下加载类。

目的:避免同一个类被多次加载,避免有重复的字节码出现,保证Java程序安全稳定运行。

双亲指的是什么:parents中文翻译的结果。其实指的是所有父类。

类加载过程

加载(ClassLoader的loadClass()方法)根据类的全限定名读取字节码到jvm内部。

将Class文件转换为运行时数据结构(存储到方法区中)

转换为一个与目标类型对应的java.lang.Class对象

类加载方式命令行启动应用时候由JVM初始化加载

通过Class.forName()方法动态加载,默认会执行初始化块,执行类中的static块(jdbc driver)

通过ClassLoader.loadClass()方法动态加载,不会执行初始化块

连接验证(文件格式验证、元数据验证、字节码验证和符号引用验证)

准备(为类中的所有静态变量分配内存空间,并为其设置一个初始默认值(由于还没有产生对象,实例变量将不再此操作范围内))

解析(将常量池中所有的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法)。这个阶段可以在初始化之后再执行)

再补充一个常识,Class.loadClass()执行完获得的对象是连接后的对象。

初始化

在连接的准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员自己写的逻辑去初始化类变量和其他资源。比如int a=6;

注意这个初始化和连接-准备阶段的初始化不同,连接-准备中初始化赋的值是基础类型的默认值,而这里是真正我们赋值的值。

类的声明周期加载

连接

初始化

使用

卸载

Java虚拟机如何结束生命周期执行System.exit()方法

程序正常执行结束

程序执行遇到异常或Error终止

操作系统出错而导致java虚拟机运行终止

何时触发初始化为一个类型创建一个新的对象实例时(比如new、反射、序列化)

调用一个类型的静态方法时(即在字节码中执行invokestatic指令)

调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式

调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法)

初始化一个类的派生类(子类)时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外)

JVM启动包含main方法的启动类时。

注意:通过子类引用付了的静态字段,不会导致子类初始化。

自定义类加载器

要创建用户自己的类加载器,只需要继承java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,即指明如何获取类的字节码流。

如果要符合双亲委派规范,则重写findClass方法(用户自定义类加载逻辑);要破坏的话,重写loadClass方法(双亲委派的具体逻辑实现)。

JAVA热部署实现

首先谈一下何为热部署(hotswap),热部署是在不重启 Java 虚拟机的前提下,能自动侦测到 class 文件的变化,更新运行时 class 的行为。Java 类是通过 Java 虚拟机加载的,某个类的 class 文件在被 classloader 加载后,会生成对应的 Class 对象,之后就可以创建该类的实例。默认的虚拟机行为只会在启动时加载类,如果后期有一个类需要更新的话,单纯替换编译的 class 文件,Java 虚拟机是不会更新正在运行的 class。如果要实现热部署,最根本的方式是修改虚拟机的源代码,改变 classloader 的加载行为,使虚拟机能监听 class 文件的更新,重新加载 class 文件,这样的行为破坏性很大,为后续的 JVM 升级埋下了一个大坑。

另一种友好的方法是创建自己的 classloader 来加载需要监听的 class,这样就能控制类加载的时机,从而实现热部署。

热部署步骤:销毁自定义classloader(被该加载器加载的class也会自动卸载);

更新class

使用新的ClassLoader去加载class

JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。

加载该类的ClassLoader已经被GC。

该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法

可见性机制及单一性机制

可见性机制:子类加载器可以看到父类加载器加载的类,而反之则不行.

单一性机制:父加载器加载过的类不能被子加载器加载第二次。虽然重写违反委托和单一性机制的类加载器是可能的,但这样做并不可取。

深入loadClass 源码

public Class> loadClass(String name)throws ClassNotFoundException {

return loadClass(name, false);

}

protected synchronized Class> loadClass(String name, boolean resolve)throws ClassNotFoundException {

// 首先判断该类型是否已经被加载 Class c = findLoadedClass(name);

if (c == null) {

//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载 try {

if (parent != null) {

//如果存在父类加载器,就委派给父类加载器加载 c = parent.loadClass(name, false);

} else {

//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name) c = findBootstrapClass0(name);

}

} catch (ClassNotFoundException e) {

// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能 c = findClass(name);

}

}

if (resolve) {

resolveClass(c);

}

return c;

}

}

可以看到,这个方法维护了一个双亲委派模型,所以我们在自定义ClassLoader时,尽量不要重写loadClass方法,除非你要破坏这个模型。

关注公众号【程序论】,领取海量的学习资源,还有免费的面试指南等你来哦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值