Java虚拟机的架构
- 1.内存结果概述
- 2.类加载器子系统的作用
- 3. 类加载器ClassLoader角色
- 4.类的加载过程
- 5.类加载器的分类
- 1.引导类加载器(虚拟机自带的加载器)Bootstrap ClassLoader
- 2.扩展类加载器(虚拟机自带的加载器) Extenssion ClassLoader
- 3.应用程序类加载器(虚拟机自带的加载器)AppClassLoader
- 4.用户自定义类加载器
- 5.关于ClassLoader
1.内存结果概述
Java虚拟机那么复杂,它的基本结构是什么?各个组成部分有何作用?又是如何相互协调工作的呢?要解答这些问题就必须先了解Java堆、Java栈、永久区和元数据区的基本概念。
![在这里插入图片描述](https://img-blog.csdnimg.cn/e72bd95094bc4af1bb277a4ae5687474.jpeg
2.类加载器子系统的作用
1.类加载器子系统负责从文件或网络中加载Class文件,class文件在文件的开头有特定的文件标识。
2.ClassLoader只负责class文件的加载,至于类是否可运行,则有ExectionEngine决定。
3.加载的类的信息存放于一个称为方法区的内存空间,除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
3. 类加载器ClassLoader角色
1.class file 存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到jvm当中来根据这个文件实例 化出n个一模一样的实例。
2.class文件加载到JVM中,被称为DNA元数据模板,放在方法区。
3.在类文件.class文件到->JVM->元数据模板,此过程就需要一个运输工具,类装载器Class Loader,扮演着一个快递元的身份。
恶意攻击有可能对字节码文件进行修改。
4.类的加载过程
加载:
1.通过一个类的全限定命名获取定义此类的二进制字节流
2. 将这个字节流代表的静态存储结构转化为方法区的运行时数据结构
3. 在内存中生成一个代表这给类的java.lang.Class对象,做为方法区这个类的各种数据访问入口。
加载.class的方式
1.从本地系统中获取。
2.从网络中获取。典型场景:Web Applet。
3.从zip压缩包中获取,称为日后jar,war格式的基础。
4.运行时计算生成,典型的场景:jsp应用
5.从专有数据库中提取.class文件
6.从加密文件中获取,典型的防Class文件被反编译的保护措施.
验证(Verify):
1.目的在于确保Class文件的字节流中包含的符号信息符合虚拟机的要求,保证被加载类的正确性,不会危害虚拟机的自身安全。
2.主要包含四种验证:文件格式的验证,元数据验证,字节码验证,符号引用验证。
准备(Prepare):
1.为类变量分配内存并设置默认初始值,即零值。
2.这里不包含final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化。
3.这里不会为实例变量分配初始化,类变量会分配在方法去中,而实例变量是会随着对象一起分配到java堆内存中。
解析(Resolve):
1.将常量池内的符号引用转换为直接引用的过程。
2.事实上,解析操作往往会伴随着Java虚拟机在执行完初始之后再执行。
3.符号引用就是一组符号用来描述所有引用的目标。符号引用的字面量形式明确定义在<java虚拟机规范>的class文件格式中,直接引用就是直接指向目标的指针,相对的偏移量或一个间接定位到目标的句柄。
4.解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_CLASS_info、CONSTANT_Fieldref_info、CONSTANT_Methoftrf_info等。
初始化:
1.初始化阶段几十执行类构造器方法()的过程
2.此方法不需要定义,是javac编译器自收集类中所有类变量的赋值动作和静态代码块中的语句合并起来。
3.构造器方法中指令按语句在源文件中出现的顺序执行。
4.()不同于类的构造器
5.若该类具有父类,jvm会保证子类()执行前,父类的()已经执行完毕。
6.虚拟机必须保证一个类的()方法在多线程下被同步加锁。
若类中没有静态变量或静态代码块,类构造器方法()就不会生成。
任何一个类声明以后,内部至少存在一个类构造器()
IDEA安装jclasslib Bytecos Viewer插件可以查看编译后的字节码文件内容,插件安装后重启IDEA
查看编译后的字节码文件
下面代码的执行顺序为:ClassInintTest类加载完成—>调用静态方法main()–>调用Son—>加载并初始化Father—>加载并初始化Son—>结果等于2
先引入A值,再引入B值
5.类加载器的分类
1.JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader) 和自定义类加载器(User-Defind ClassLoader).(JAVA的核心类库都是引导类加载器加载的)
2.从概念上来讲,自定义类加载器以一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范并没有这么定义,而是将所有派生出来的抽象类ClassLoader的类加载器都划分为自定义类加载器。
3.无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个:
1.引导类加载器(虚拟机自带的加载器)Bootstrap ClassLoader
1.这个类加载使用C/C++的语言实现的,嵌套在JVM内部。
2.它用来加载Java的核心类库,(JAVA_HOME/jre/lib/rt.jar resources.jar 或sun,boot.class.path路径下的内容),用于提供JVM自身需要的类.
3.并不继承自java.lang.ClassLoader,没有父加载器。
4.加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
5.出于安全考虑,Bootstrap 启动类加载器只加载名为Java、javax、sun等开头的类。
获取BootstrapClassLoader能够加载的api路径
2.扩展类加载器(虚拟机自带的加载器) Extenssion ClassLoader
1.Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
2.派生于ClassLoader类
3.父类加载器为启动类加载器。
4.从java.ext.dirs系统属性锁指定的目录中加载类库,或从JDK的安装目录的jre/lib.ext子目录(扩展目录)下载类库,如果用户创建的JAR放在此目录下,也会自动i用由扩展类加载器加载。
3.应用程序类加载器(虚拟机自带的加载器)AppClassLoader
1.java语言编写,由sun.misc.Launcher A p p C l a s s L o a d e r 实现。 2. 派生于 C l a s s L o a d e r 类。 3. 父类加载器为扩展类加载器。 4. 它负责加载环境变量 c l a s s p a t h 或系统属性, j a v a . c l a s s . p a t h 指定路径下的类库。 5. 该类加载器是程序中默认的类加载器,一般来说, J a v a 应用的类都是由它来完成加载。 6. 通过 C l a s s L o a d e r AppClassLoader实现。 2.派生于ClassLoader类。 3.父类加载器为扩展类加载器。 4.它负责加载环境变量classpath或系统属性,java.class.path指定路径下的类库。 5.该类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载。 6.通过ClassLoader AppClassLoader实现。2.派生于ClassLoader类。3.父类加载器为扩展类加载器。4.它负责加载环境变量classpath或系统属性,java.class.path指定路径下的类库。5.该类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载。6.通过ClassLoadergetSystemClassLoader()方法可以获取到该类加载器。
4.用户自定义类加载器
在java的日常应用程序开发中,类加载器几乎是由上述3中类加载器相互配合执行的,在必要=时,我们还可以自定义类加载器,来定制类的加载方式。
为什么要用自定义类加载器呢。
1.隔离加载类(例如系统jar包与中间jar不冲突,由于可能存在类冲突,未必免冲突)
2.修改类加载的方(除了引导类加载器BootstrapLoader加载所需的核心类之外有些类不是必须的,可以等到需要用的时候在引入)
3.扩展加载源(加载的类可以是本地的物理磁盘也可以是网络或者数据库等其他方式加载字节码文件来源)
4.防止源码泄露
用户自定义类加载器实现的步骤:
1.开发人员可以通过继承抽象类java.lang.ClassLoader类的方式,实现直自己的类加载器,以满足一些特殊的需求。
2.在JDK1.2之前,在自定义类加载器时,总会取继承ClassLoader类并重些写loadClass()方法,从而实现自定义类加载,但是在JDK1.2之后
已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写再findClass()方法中。
3.在编写自定义类加载器时,如过没有太过于复杂的需求,可以直接继承URLClassLoader类。这样就可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更简洁。
import java.io.FileNotFoundException;
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] result = getClassFromCustomPath(name);
if (result == null) {
throw new FileNotFoundException();
} else {
return defineClass(name, result, 0, result.length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
throw new ClassNotFoundException(name);
}
private byte[] getClassFromCustomPath(String name){
//从自定义路径中加载指定类:细节略
//如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
return null;
}
public static void main(String[] args) {
CustomClassLoader customClassLoader = new CustomClassLoader();
try {
Class<?> clazz = Class.forName("One",true,customClassLoader);
Object obj = clazz.newInstance();
System.out.println(obj.getClass().getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.关于ClassLoader
ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
getParent(); 返回该类加载器的超类加载器。
loadClass(String name) 加载名称为name的类,返回结果为java.lang.Class类的实例。
findClass(String name) 查找名称为name的类,返回结果为java.lang.Class类的实例。
findLoadedClass(String name) 查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例。
defindClass(String name ,bytr[] b,int off,int len) 把字节数组b中的内容转换为一个Java类,返回结果为java.lang.Class类的实例,
resolveClass(Class<?> c) 链接一个指定的java类。