深入了解Java之类加载和案例分析

在讨论JVM内存区域分析之前,先来看一下Java程序具体执行的过程:

深入了解Java之类加载和案例分析_第1张图片
Java程序执行过程

Java 程序的执行过程:Java 源代码文件(.Java文件)-> Java Compiler(Java编译器)->Java 字节码文件(.class文件)->类加载器(Class Loader)->Runtime Data Area(运行时数据)-> Execution Engine(执行引擎)。上篇文章已经分析了 Runtime Data Area(运行时数据) 这一块,那我们今天就来分析一下Java程序执行过程的 类加载器(Class Loader) 这一块

一、类加载器(ClassLoader)

把Java类的数据从Class文件加载到虚拟机内存中,然后对这部分数据进行验证、准备、解析、初始化,最终形成可以被虚拟机直接使用的Java类型。

学习这个有什么好处呢?

1、有助于了解Java虚拟机的执行过程

2、程序能动态的控制类加载,比如热部署、热修复等

类加载器(ClassLoader)的一些方法:

方法 说明
getParent() 返回该类加载器的父类加载器
loadClass(String name) 加载名称为 name的类
findClass(String name) 查找名称为 name的类
findLoadedClass(String name) 查找名称为 name的已经被加载过的类
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的

二、类加载的分类

  • Bootstrap Loader(启动类加载器) :是用C++语言写的,是虚拟机自身的一部分,主要负责加载Java的核心类;

  • Extended Loader(扩展类加载器) :Extended Loader的父加载器为 Bootstrap Loader,该加载器使用Java写的,它用来加载Java的扩展库;

  • AppClass Loader(系统类加载器):AppClass Loader的父加载器为 Extended Loader,该加载器使用Java写的,它用来加载Java应用的类路径(CLASSPATH)上的类库。

三、类加载过程

寻找jre目录,寻找jvm.dll,并初始化JVM,JVM启动后,运行Bootstrap Loader,该Bootstrap Loader(启动类加载器)自动加载Extended Loader(扩展类加载器)和AppClass Loader(系统类加载器),最后AppClass Loader加载CLASSPATH目录下定义的Class。

四、双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

代码实现:

protected Class loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //检查该类是否已经加载过
        Class c = findLoadedClass(name);
        if (c == null) {
            //如果该类没有加载,则进入该分支
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //当父类的加载器不为空,则通过父类的loadClass来加载该类
                    c = parent.loadClass(name, false);
                } else {
                    //当父类的加载器为空,则调用启动类加载器来加载该类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //非空父类的类加载器无法找到相应的类,则抛出异常
            }

            if (c == null) {
                //当所有父类加载器无法加载时,则调用自己findClass方法来加载该类
                long t1 = System.nanoTime();
                c = findClass(name); //用户可通过覆写该方法,来自定义类加载器

                //用于统计类加载器相关的信息
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            //对类进行link操作
            resolveClass(c);
        }
        return c;
    }
}

"双亲委派模型"有什么作用呢?

1、这样保证了每个类都只会加载一次

2、维护系统自身类的安全

五、类加载的三种方式

1、通过JVM初始化加载

2、通过Class.forName()方法动态加载

3、通过ClassLoader.loadClass()方法动态加载

六、案例分析

基于上篇文章的"深入了解Java之虚拟机内存"和这篇"深入了解Java之类加载"的理论知识,我们用这个例子简单的来了解一下类的加载和内存分配的一些逻辑:

public class TestMain {
     public void TestMethod() {
         Sample testSample = new Sample(" test ");
         testSample.getName();

     }
 }
 public class Sample {
     private String name;
     public Sample(String name) {
         this.name = name;
     }
     public void getName() {
         Log.i("Sample", "name:" + name);
     }
 }

过程分析

public class TestMain

通过JVM初始化加载;通过Class.forName()方法动态加载;通过ClassLoader.loadClass()方法动态加载通过以上方法加载这个类的时候,会从Bootstrap Loader(启动类加载器),Extended Loader(扩展类加载器)从上往下加载,发现都加载不了,最后由AppClass Loader从CLASSPATH中找到加载TestMain这个类,读取这个文件中的二进制数据,接着对这部分数据进行验证、准备、解析、初始化,最后会把这个类的类信息、常量、静态变量、以及编译器编译后的代码等存入运行时数据区中的方法区。

public void TestMethod()

TestMethod 也被放入方法区。

Sample testSample = new Sample(" test ");

先看"="右边的:“new Sample(" test ")”也是一个类跟上面一样,加载这个类的相关信息到方法区中,然后就是就是要在运行时数据区中的堆进行分配内存,方法区的类信息和堆怎么对应呢?其实堆只是存放了这个Sample实例的一个指向方法区的引用地址testSample,他主要是存放在运行时数据的Java虚拟机栈的栈帧中的局部变量表中"="其实不是赋值,而是testSample这个变量指向了堆中的Sample实例

testSample.getName();

testSample指向了堆中Sample的实例,然后根据这个实例持有的引用拿到了方法区这个类的相关信息,其中的一个方法getName();接着执行这个方法打印数据

分析完毕,谢谢大家耐心看完,有什么不对的地方,欢迎指出,谢谢! 个人博客地址

你可能感兴趣的:(深入了解Java之类加载和案例分析)