ClassLoader类加载分析(二)

一、JVM 提供的 Classloader 1.1 BootstrapClassloader 引导类加载器,又称启动类加载器,是最顶层的类加载器,主要用来加载Java核心类,如rt.jar、resources.jar、charsets.jar等,Sun的JVM中,执行java的命令中使用-Xbootclasspath选项或使用- D选项指定sun.boot.class.path系统属性值可以指定附加的类,它不是 java.lang.ClassLoader的子类,而是由JVM自身实现的该类c 语言实现,Java程序访问不到该加载器。通过下面代码可以查看该加载器加载了哪些jar包 public class MainClass { public static void main(String[] args) throws ClassNotFoundException { URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); Arrays.stream(urls).map(URL::toExternalForm).forEach(System.out::println); } } 执行结果: file:/C:/java/jdk1.8.0_74/jre/lib/resources.jar file:/C:/java/jdk1.8.0_74/jre/lib/rt.jar file:/C:/java/jdk1.8.0_74/jre/lib/sunrsasign.jar file:/C:/java/jdk1.8.0_74/jre/lib/jsse.jar file:/C:/java/jdk1.8.0_74/jre/lib/jce.jar file:/C:/java/jdk1.8.0_74/jre/lib/charsets.jar file:/C:/java/jdk1.8.0_74/jre/lib/jfr.jar file:/C:/java/jdk1.8.0_74/jre/classes, 写到这里大家应该都知道,我们并没有在classpath里面指定这些类的路径,为啥还是能被加载到jvm并使用起来了吧,因为这些是bootstarp来加载的。 1.2 ExtClassloader 扩展类加载器,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar包或者由java.ext.dirs系统属性指定的jar包。放入这个目录下的jar包对所有AppClassloader都是可见的(后面会知道ExtClassloader是AppClassloader的父加载器)。那么ext都是在那些地方加载类内: System.out.println(System.getProperty("java.ext.dirs")); C:\java\jdk1.8.0_74\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext 1.3 AppClassloader 系统类加载器,又称应用加载器,本文说的SystemClassloader和APPClassloader是一个东西,它负责在JVM启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。调用ClassLoader.getSystemClassLoader()可以获取该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器,这点通过ClassLoader的无参构造函数可以知道如下: protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } 执行以下代码即可获得classpath加载路径: System.out.println(System.getProperty("java.class.path")); 1.4 Java中如何构造三种类加载器的结构 下面从源码来分析下JVM是如何构建内置classloader的,具体是rt.jar包里面sun.misc.Launcher类: public class Launcher { private static Launcher launcher = new Launcher(); private static String bootClassPath = System.

ClassLoader类加载分析(一)

一、什么是Classloader 一个Java程序要想运行起来,首先需要经过编译生成 .class文件,然后创建一个运行环境(jvm)来加载字节码文件到内存运行,而.class 文件是怎样被加载中jvm 中的就是Java Classloader所做的事情。 那么.class文件什么时候会被类加载器加载到jvm中运行那?比如执行new操作时候,当我们使用Class.forName(“包路径+类名”),Class.forName(“包路径+类名”,classloader),classloader.loadclass(“包路径+类名”);时候就触发了类加载器去类加载对应的路径去查找*.class,并创建Class对象。 类的加载过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括: 1、装载 2-4、链接 -包括 【验证、准备、解析】 5、初始化 6、使用 7、卸载 其中 链接(Link)又分3个步骤,如图所示。类加载到卸载的生命周期流程图如下: 1) 装载:查找并加载类的二进制数据(查找和导入Class文件) 加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 1、通过一个类的全限定名来获取其定义的二进制字节流(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等)。 2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。 相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。 加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。 2) 链接(分3个步骤) 1、验证:确保被加载的类的正确性 验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作: 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。 符号引用验证:确保解析动作能正确执行。 验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。 2、准备:为类的静态变量分配内存,并将其初始化为默认值 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意: 1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。 2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。 l例如在准备阶段,为类变量(static修饰)在方法区中分配内存并设置初始值。 private static int var = 50; 准备阶段完成后,var 值为0,而不是50。在初始化阶段,才会把50赋值给val,但是有个特殊情况: private static final int var= 50; 在编译阶段会为var生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将var赋值为50。 3、解析:把类中的符号引用转换为直接引用 解析阶段是将常量池中的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行.。符号引用和直接引用有什么不同? 1、符号引用 :使用一组符号来描述所引用的目标,可以是任何形式的字面常量,定义在Class文件格式中。 2、直接引用 :可以是直接指向目标的指针、相对偏移量或则能间接定位到目标的句柄。 **3) 初始化: 初始化阶段是执行类构造器方法的过程,方法由类变量的赋值动作和静态语句块按照在源文件出现的顺序合并而成,该合并操作由编译器完成。 public class MuitiThreadInit { private static int value = 100; static int a = 100; static int b = 100; static int c; static { c = a + b; System.