JVM,Java类的加载机制

JVM的运行机制概要:

如果要运行Java程序,首先要编写Java源文件(* .java),之后Java的编译器将源文件编译成字节码文件(* .class文件),之后再通过类加载器将字节码文件加载到JVM中,其中与程序运行最密切的是JVM中的运行时数据区(Runtime data area)
JVM,Java类的加载机制

运行时数据区(JVM内存模型)

程序的运行最为重要的一块区域就是运行时数据区,其中运行时数据区可以分为如下几个子区域:
JVM,Java类的加载机制
程序计数器:
程序计数器是用于存放吓一跳指令所在单元的地址的地方
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行虚拟机字节码指令的地址
虚拟机栈:
该区域是线程私有的,虚拟机的生命周期和线程相同,在执行一个Java方法的时候会在该区域创建一个栈帧,栈帧用来 保存局部变量表(在方法中定义的变量或者方法的参数都是方法的局部变量),动态链接,方法出口操作数栈等等信息,其实每个方法从执行到执行完毕就是一个入栈与出栈的过程
当入栈的速度大于出栈的速度的时候会出现栈内存溢出(Exception in thread “main” java.lang.*Error)
本地方法栈:
本地方法栈和虚拟机栈的作用很相似,不同点是虚拟机栈是为Java方法服务的,本地方法栈是为native方法服务的(这种本地方法是没有方法体的,可以算是一个抽象方法,具体的实现是本地操作系统的c语言写的一些函数库中的方法实现的)
堆:
堆是运行时数据区中最大的一块子区域,创建的实例化对象和数组都需要在堆内存开辟空间,堆空间是线程共享的区域,该区域也是gc(垃圾回收)重要处理的一个区,所以堆又被称为“GC堆”
方法区:
这是对永久代的实现,永久代是在JDK1.8之前出现的概念,在JDK1.8开始就没有了永久代,而是使用了元空间代替了
方法区也是一块线程共享的区域,该区域主要保存的是已经加载到JVM中的类的信息,常量,静态变量等等

java类的加载机制

类的加载指的是将类的 .class文件的二级制数据读入到内存中,
1.将类信息保存在方法区内(在jdk1.8之前,在jdk1.8开始就用元空间代替)
2.让后在堆区创建一个Java.lang.Class对象,用来封装类在方法区类的数据结构,可以使用该对象操作方法区中类的所有信息
类的加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构,并且提供了访问方法区内的数据结构的接口(简单理解为方法)
1.加载会将类的一些数据的结构信息保存到方法区
2.之后会在堆区创建一个对象用于操作获取方法区中对应的类的数据以及信息等,每个类都会在加载的时候创建java.lang.Class类的对象,此时每个类的对应的堆区的这个对象的类型都是Class类型怎么区分呢?这个类是一个泛型类,使用泛型来区分

  • 堆区对应的这个Class类对象提供了很多方法用于操作方法区中类的数据结构
    比如说要加载一个Car类:
    1.将类的结构信息加载都爱方法区中
    2.在堆中创建一个数据Car类的Class< Car >对象,作用是提供方法让我们可以操作方法区中Car数据结构信息

    以上说了可以使用类的Class对象操作类的结构信息,你要使用对象应该先取得对象,这个Class对象怎么取得呢?有三种方法可以取得:
  • 第一种方式:使用对象的getClass()方法,类可以有自己的普通对象(new出来的),可以使用这个对象的getClass()方法,这个方法是继承自最大父类Object
  • 第二种方式:使用Class类的一个静态方法:forName(“类的名”)
  • 第三种方式:可以直接使用“类名.class”直接取得

这三种方式最终取得的是在加载的时候保存到堆区类对应的Class类对象。取得之后就可以调用Class类对象的方法操作保存在方法区中的类的信息了
操作方法区中的类的构造方法

public class Test  {
    public static void main(String[] args) throws  Exception {
       Class<?>  classObj =  Link.class;
       classObj.newInstance(); //实例化保存在方法区中的Link类的对象(本质是调用了Link类的构造方法)
    }
}

JVM,Java类的加载机制
以上我们说了要将类的字节码文件(*.class)转换成二进制形式加载到内存中,这个加载的过程是类加载器(ClassLoader),类加载分为引导类加载器,扩展类加载器,应用程序加载器,自定义加载器

类加载器:

类加载器的作用是将*.class文件加载到内存中的组件
JVM,Java类的加载机制

  • 引导类加载器(bootstrap class loader)

    Bootstrap加载器(启动类加载器或者叫做引导类加载器)是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib

  • 扩展类加载器(extensions class loader)

    启动类加载在启动的时候会将ExtClassLoader加载进来,并且将ExtClassLoader的父加载器设置为Bootstrploader(不是使用extends的概念实现父子关系),ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext。

  • 应用程序加载器(application class loader )

    应用程序加载器又叫做系统类加载器, 启动类加载器(Bootstrploader)加载完扩展类加载器(ExtClassLoader)后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为ExtClassLoader。AppClassLoader也是用Java写成的,它的实现类是sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader,系统类加载器主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
    可以通过继承java.lang.ClassLoader的方式实现一个自定义的加载器,以满足一些特殊的需求。
    2、java.class.ClassLoader的作用

    ClassLoader主要对类的请求提供服务,当JVM需要某类时,由ClassLoader返回这个类的class对象。 ClassLoader负责载入系统的所有Resources(Class,文件,来自网络的字节流等),通过ClassLoader从而将资源载入JVM

public class Test  {
    public static void main(String[] args) throws  Exception {
        //取得系统类加载器
        System.out.println("系统类加载器:"+ClassLoader.getSystemClassLoader());
        //取得系统类加载器的父加载器
        System.out.println("系统类加载器的父加载器:"+ClassLoader.getSystemClassLoader().getParent());
        //取得扩展类加载器的加载器
        System.out.println("系统类加载器的父加载器:"+ClassLoader.getSystemClassLoader().getParent().getParent());
    }
}

JVM,Java类的加载机制

双亲委派加载机制:

1、当APPClassLoader加载一个class时,先到自己的缓存中查找,如果缓存中不存在他先不会自己去加载这个类,而是把类加载请求委派给父类加载器EXTClassloader去完成。

2、当EXTClassLoader加载一个class时,先到自己的缓存中查找,如果缓存中不存在它先不会去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3、当BootStrapClassLoader加载一个类时,先到自己的缓存中查找,如果缓存中不存在它先不会去尝试加载这个类,如果加载失败,则会使用EXTClassLoader去尝试加载。

4、若EXTClassLoader也加载失败,则会使用APPClassLoader来加载,如果APPClassLoader也加载失败,则会报出异常ClassNotFundException.

JVM,Java类的加载机制