jvm类加载机制学习基础(一)

1. 概述

Java跟C/C++这类语言最大的差别是基于Java虚拟机的,JVM可以让Java很轻松的实现跨平台运行,因为java虚拟机统一了各平台的编程规范,无论什么类型的平台,在实现上都如同在一个平台上。
 另一方面,Java语言动态扩展性很高,他的字节码程序存储在Class文件中,并且支持动态扩展和动态连接。比如:在已运行的机器上,想修改一些功能,那只需要使用类加载将接口链接到新的实现类上就可以了。
 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,就是虚拟机的类加载机制。


2.类加载的过程

    将class文件中的二进制数据读入到内存中,并将静态数据转换成方法区中的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.class对象,用来封装在方法区类的对象。

3.1 类加载系统结构

类加载子系统示意图如下:
  Class文件加载子系统结构图
 
 要加载的是字节码,字节码的内容是存放在Class文件中,所以就需要类加载器从Class二进制文件流中把字节码加载到内存中,并且转换为我们代码中能够直接操作的类型(变量,类,接口等)。

3.2 类加载过程

jvm类加载机制学习基础(一)_第1张图片

  • (1) 什么时候开始初始化?
    什么情况下需要开始类加载过程的第一个阶段:“加载”。虚拟机规范中并没强行约束,这点可以交给具体的虚拟机去实现。
    但是规定了,以下几种情况会触发类的初始化:

虚拟机规范说明,有且仅有以下五种时机时,如果类还未初始化,会触发对类的加载:

1.运行以下4种字节码指令时:
  • new: 使用new实例化对对象;
  • getstatic: 读取一个类的静态字段(不包括被final修饰、编译器已经放入常量池的静态字段不算);
  • putstatic: 对一个静态字段进行写入值;
  • invokestatic: 调用一个类的静态方法时。
2.使用java.lang.reflect包中的方法对类进行反射调用时
3.初始化一个类时,其父类还未被初始化
4.虚拟机启动时先初始化好包含main()方法的main class
5.jdk1.7,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,这个方法对应的类还未初始化则会进行初始化。

3.3 类加载时类成员的加载顺序

java中的类由静态成员和非静态成员组成,在一个类进行加载-初始化的时候顺序是怎么样的呢?
(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器;
如果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法
举例:


4. 类加载器的双亲委派模型

  • 类加载器:通过一个类的全限定名来获取描述此类的二进制字节流,应用程序决定如果去获取所需要的类,实现这个动作的代码模块称为“类加载器”。

4.1 Java的类加载器结构

jvm类加载机制学习基础(一)_第2张图片

4.2 jvm双亲委派机制的工作过程

    如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,每一个层次的类加载器都是这样工作,因此所有的类加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈无法完成类加载请求时,子类加载器才会尝试自己去加载。
ClassLoader进行类加载的方法在代码中的实现:

// loadClass:先检查是否已经被加载过,若没有则调用父类加载器的loadClass()方法,
// 若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器加载失败,则抛出
// ClasNotFoundExceptionyichang,再调用自己的findClass()方法加载
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 先检查要加载的类是否已经被加载,这里说明同一个类只会被加载一次
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) { //先判断父类加载器是否为空,不为空则用父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {//如果父类加载器也为空则用启动类加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                    //如果父类加载器进行加载找不到该类,则抛出ClassNotFoundException
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    //当父类加载器无法加载时,调用自定义的ClassLoader本身的findClass
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

注:同一个类加载器在加载同一个类时不会重复加载,认为是同一个类;不同的类加载器加载同一个类不会认为是同一个类,会重复加载。
可以从代码中看出,只要父类加载能够完成类的加载任务,类加载的工作就会是由父类加载器完成。
注:类加载器的树形结构是通过组合的方式实现,而非直接通过继承实现。

4.3 java中三种基础类加载器分析

(1)Bootstrap ClassLoader(启动类加载器):负责将存放在JAVA_HOME\jre\lib\rt.jar下的类库加载到虚拟机内存中。启动类加载器是由c,c++实现的,不能被Java代码直接引用。

(2)Extension ClassLoader(扩展类加载器):由sun.misc.Launcher$ExtClassLoader实现,负责加载*JAVA_HOME\bin\ext*.jar目录下,或者被java.ext.dirs系统变量指定的路径中的所有类库。
jvm类加载机制学习基础(一)_第3张图片

(3)Application ClassLoader(应用程序类加载器):由sun.misc.Launcher$AppClassLoader实现。负责加载用户类路径(classpath)上所指定的类库。是应用程序中默认的类加载
jvm类加载机制学习基础(一)_第4张图片

2.获得各层级ClassLoader实例测试:
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();//AppClassLoader
ClassLoader extClassLoader = appClassLoader.getParent();//ExtClassLoader
ClassLoader systemClassLoader = extClassLoader.getParent();//SystemClassLoader

输出:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@14ae5a5
null //native层实现的,是没有java地址的,到是会有个native的指针
3.通过代码测试三种类加载器加载哪些路径下的class文件
public class MainTest {
    public static void main(String[] args) {
        System.out.println("启动类加载器路径:\n" + System.getProperty("sun.boot.class.path"));
        System.out.println("扩展类加载器路径:\n" + System.getProperty("java.ext.dirs"));
        System.out.println("系统类加载器路径:\n" + System.getProperty("java.class.path"));
    }
}
打印输出结果如下:
  • a:启动类加载器路径:
/usr/lib/jdk1.8.0_201/jre/lib/resources.jar:
/usr/lib/jdk1.8.0_201/jre/lib/rt.jar:
/usr/lib/jdk1.8.0_201/jre/lib/sunrsasign.jar:
/usr/lib/jdk1.8.0_201/jre/lib/jsse.jar:
/usr/lib/jdk1.8.0_201/jre/lib/jce.jar:
/usr/lib/jdk1.8.0_201/jre/lib/charsets.jar:
/usr/lib/jdk1.8.0_201/jre/lib/jfr.jar:
/usr/lib/jdk1.8.0_201/jre/classes
  • b:扩展类加载器路径:
/usr/lib/jdk1.8.0_201/jre/lib/ext:
/usr/java/packages/lib/ext
  • c:应用类加载器路径:
//第一类:jre/lib下的所有jar包和jre/lib/ext下所有jar包
/usr/lib/jdk1.8.0_201/jre/lib/charsets.jar:
/usr/lib/jdk1.8.0_201/jre/lib/...:
/usr/lib/jdk1.8.0_201/jre/lib/ext/cldrdata.jar:
/usr/lib/jdk1.8.0_201/jre/lib/ext/...:
//第二类:当前项目编译生成的class文件
/media/yanggui/4477AE83244A935F/study_dir/java_project/out/production/classloader:
/home/yanggui/dev_tool/idea-IU-183.5912.21/lib/idea_rt.jar

发现扩展类加载器和应用类加载,加载路径上居然有重叠的部分,没懂,知道的大佬望告知一声。

4.4 三类基础类加载器比较
ClassLoader类型 加载路径 实现类 是否可以直接使用
启动类加载器 System.getProperty(“java.ext.dirs”) native实现
扩展类加载器 System.getProperty(“java.ext.dirs”) ExtClassLoader
应用类加载器 System.getProperty(“java.class.path”) AppClassLoader 是,应用程序默认的加载器

5.Android种常见类加载器结构

Android系统中的类加载器结构.jvm类加载机制学习基础(一)_第5张图片
PathClassLoader : 从本地文件系统加载系统类和应用层类,不能从网络加载class字节码
DexClassLoader : 从包含classes.dex文件的.jar或.apk文件中加载class字节码

你可能感兴趣的:(Android基础,java语言编程基础,Java虚拟机,java,类加载机制,jvm,android)