深入探究jvm之类装载器

一、class装载验证流程

1、加载

  1)、取得类的二进制流。

  2)、转为方法区数据结构。

  3)、在Java堆中生成对应的java.lang.Class对象。

2、链接--验证(目的:保证Class流的格式是正确的)

  1)、文件格式的验证:是否是0xCAFEBASE开头、版本号是否正确等。

  2)、元数据验证:是否有父类、是否继承了final类、非抽象类是否实现了所有的抽象方法等。

  3)、字节码验证(最复杂):运行检查、栈数据类型和操作码数据参数是否吻合、跳转指令是否指定到合理的位置。

  4)、符号引用验证:常量池中描述类是否存在、访问的字段和方法是否存在且有足够的权限。

3、链接--准备

  分配内存,并为类设置初始值(在方法区中),举个栗子:

  ①public static int a = 100;在链接--准备阶段中,a会被设置为0(int 类型的默认值),在初始化的<clinit>中才会被设置为1。

  ②public static final int a = 100;对于static final 类型,在链接--准备阶段就会被赋上正确的值,a被设置为100。

4、链接--解析

  符号引用(字符串引用对象不一定被加载)替换为直接引用(指针或者偏移量,引用对象一定存在于内存中)。

5、初始化

  1)、执行类构造器<clinit>,包括static变量赋值语句和static块。、

  2)、子类的<clinit>调用前保证父类的<clinit>被调用。

  3)、<clinit>方法是线程安全的,同步执行。

二、什么是ClassLoader?

  ClassLoader是一个抽象类,它的实例将读入的Java字节码装载到jvm中,ClassLoader可以实现定制,以满足不同的字节码流的获取方式,主要负责类装载过程中的加载。

  1)ClassLoader中重要的方法:

public Class<?> loadClass(String name) throws ClassNotFoundException
载入并返回一个Class

protected final Class<?> defineClass(byte[] b, int off, int len)
定义一个类,不公开调用

protected Class<?> findClass(String name) throws ClassNotFoundException
loadClass回调该方法,自定义ClassLoader的推荐做法

protected final Class<?> findLoadedClass(String name)
寻找已经加载的类

   2)JDK中ClassLoader默认的设计模式分类:

   ①BootStrap ClassLoader(启动ClassLoader)

   ②Extension ClassLoader (扩展ClassLoader)

   ③App ClassLoader(应用ClassLoader/系统ClassLoader)

   ④自定义ClassLoader

  上图展现了类查找和加载的次序,这样我们很容易就会想到存在一个问题:顶层的ClassLoader是无法加载底层ClassLoader的类,只就是“双亲问题”。那么Java框架(也就是rt.jar)如何加载Classpath下应用的类呢?

  举个栗子进一步说明问题:javax.xml.parsers包中定义了xml解析的类接口,这些类接口(Service Provider Interface)都位于rt.jar,即接口的定义(以及类的工厂方法)都在Bootstrap ClassLoader中,其实这个SPI的实现(非抽象类)都在AppLoader之中,JDK要求Bootstrap ClassLoader能够加载Classpath下的类,这显然是不满足上图的要求的。

  JDK为了解决这个问题在Thread类中定义了一个静态方法,Thread.setContextClassLoader()。这是一个上下文加载器,是一个“角色”,并不是一个真正的ClassLoader,它只是承担了特殊的任务,用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题。基本思想是在顶层ClassLoader中传入一个底层的ClassLoader实例。下面的代码来源于rt.jar中javax.xml.parsers.FactoryFinder展示如何在启动类加载器中加载AppLoader的类突破“双亲模式”问题。

static private Class getProviderClass(String className, ClassLoader cl,
        boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
{
    try {
        if (cl == null) {
            if (useBSClsLoader) {
                return Class.forName(className, true, FactoryFinder.class.getClassLoader());
            } else {
                cl = ss.getContextClassLoader();
                if (cl == null) {
                    throw new ClassNotFoundException();
                }
                else {
                    return cl.loadClass(className); //使用上下文ClassLoader
                }
            }
        }
        else {
            return cl.loadClass(className);
        }
    }
    catch (ClassNotFoundException e1) {
        if (doFallback) {
            // Use current class loader - should always be bootstrap CL
            return Class.forName(className, true, FactoryFinder.class.getClassLoader());
        }
…..

  双亲模式是默认的模式,但不是必须要这么做,比如Tomcat的WebappClassLoader就会先加载自己的class,找不到再委托parent,再如,OSGi(模块化,热加载)的ClassLoader形成网状结构,根据需要自由加载Class。

  破坏双亲模式的例子,先从底层的ClassLoader加载。OrderClassLoader的部分实现:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // First, check if the class has already been loaded
    Class re=findClass(name);
    if(re==null){
        System.out.println(“无法载入类:”+name+“ 需要请求父加载器");
        return super.loadClass(name,resolve);
    }
    return re;
}

  findClass(String name )实现如下:

protected Class<?> findClass(String className) throws ClassNotFoundException {
Class clazz = this.findLoadedClass(className);//每个类只加载一次,会查找、定义、加载
if (null == clazz) {
    try {
        String classFile = getClassFile(className);
        FileInputStream fis = new FileInputStream(classFile);
        FileChannel fileC = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel outC = Channels.newChannel(baos);
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
         //-----------------省略部分代码-------------------//
        fis.close();
        byte[] bytes = baos.toByteArray();

        clazz = defineClass(className, bytes, 0, bytes.length);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
return clazz;
}

  

  

原文地址:https://www.cnblogs.com/liuyk-code/p/10364158.html

时间: 2024-10-12 02:21:34

深入探究jvm之类装载器的相关文章

探究JVM——垃圾回收

垃圾回收主要考虑三件事情:哪些内存需要回收?什么时候回收?如何回收? 一.哪些内存需要回收? 堆内存:对于JVM 来说,垃圾回收主要是针对堆内存中的对象实例. 方法区:垃圾收集行为在方法区是比较少出现的,一般来说,这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是必要的. 二.什么时候回收? 在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(即不可能再被任

深入探究JVM(2) - 探秘Metaspace

Java 8彻底将永久代移除出了HotSpot JVM,将其原有的数据迁移至Java Heap或Metaspace.这一篇文章我们来总结一下Metaspace(元空间)的特性.如有错误,敬请指出,谢谢~ 一.引言:永久代为什么被移出HotSpot JVM了? 详见:JEP 122: Remove the Permanent Generation 原因主要有两个: 1.由于Permanent Generation内存经常不够用或发生内存泄露,引发恼人的java.lang.OutOfMemoryEr

深入探究jvm之GC的算法及种类

一.GC基本概念 GC(Garbage Collection)垃圾收集,1960年最早在List中使用.在Java中GC回收的对象是堆空间和永久区,可以有效避免程序员人为造成内存泄漏问题.将堆空间和永久区没有作用的对象进行释放和回收. 二.GC算法 1.引用计数法: 是一种老牌的垃圾回收算法,通过引用计算来回收垃圾,被COM.ActionScript3.Python所使用. 引用计数法的实现很简单,对于一个对象A,只要有任何一个对象引用了A,那么A的引用计数器就会+1,当引用失效时,引用计数器就

探究JVM——运行时数据区

最近在读<深入理解Java虚拟机>,收获颇丰,记录一下,部分内容摘自原书. Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁.Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示: 其中程序计数器.VM栈.native栈为线程隔离的,堆(Heap)和方法区(Method Aera,or Non-Heap)为线程共享的

JVM学习之常用概念

方法区     当JVM使用类装载器装载某个类时,它首先要定位对应的class文件,然后读入这个class文件,最后,JVM提取该文件的内容信息,并将这些信息存储到方法区,最后返回一个class实例.      方法区是系统分配的一个内存逻辑区域,是用来存储类型信息的(类型信息可理解为类的描述信息),方法区主要有以下几个特点:      一.方法区是线程安全的.由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的.例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个

JVM原理机制笔记

1.JVM 简介 JVM 全称是Java Virtual Machine ,Java 虚拟机,也就是在计算机上再虚拟一个计算机. 这和我们使用 VMWare 不一样,那个虚拟的东西你是可以看到的,这个JVM 你是看不到的,它存在内存中. 计算机的基本构成是:运算器.控制器.存储器.输入和输出设备. JVM 也是有这成套的元素: 运算器 -当然是交给硬件CPU 还处理了       为了适应"一次编译,随处运行"的情况,需要做一个翻译动作,于是就用了JVM 自己的命令集. 这与汇编的命令

JVM加载class文件的原理机制

JVM加载class文件的原理机制 1.Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的, 类装载器所做的工作实质是把类文件从硬盘读取到内存中 2.java中的类大致分为三种:     1.系统类     2.扩展类     3.由程序员自定义的类 3.类装载方式,有两种     1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,     2.显式装载, 通过class.forname()等方法,

深入理解JVM内幕:从基本结构到Java 7新特性

转自:http://www.importnew.com/1486.html 每个Java开发者都知道Java字节码是执行在JRE((Java Runtime Environment Java运行时环境)上的.JRE中最重要的部分是Java虚拟机(JVM),JVM 负责分析和执行Java字节码.Java开发人员并不需要去关心JVM是如何运行的.在没有深入理解JVM的情况下,许多开发者已经开发出了非常多的优秀 的应用以及Java类库.不过,如果你了解JVM的话,你会更加了解Java的,并且你会轻松解

什么是jvm?

说明:做java开发的几乎都知道jvm这个名词,但是由于jvm对实际的简单开发的来说关联的还是不多,一般工作个一两年(当然不包括爱学习的及专门做性能优化的什么的),很少有人能很好的去学习及理解什么是jvm,以及弄清楚jvm的工作原理,个人认为这块还是非常有必要去认真了解及学习的,特别是刚入门或入门不久的java开发来说,这是java的基石. JVM(Java Virtual Machine,Java虚拟机) Java程序的跨平台特性主要是指字节码文件可以在任何具有Java虚拟机的计算机或者电子设