文档章节

java 类加载器

H
 Haloooooo
发布于 2017/08/16 23:18
字数 2884
阅读 10
收藏 1

项目上有个不影响但是会报错的bug

 是关于类被重复加载的问题 。但是是出现在tomcat运行时出现的问题 。也就是项目和tomcat类加载的冲突。但是也解决不了于是来重新复习下类加载。

 

一 类加载的时机

    从类加载到卸载出内存共有7个阶段 :

    加载

    验证

    准备

    解析

    初始化

    使用

    卸载

其中验证 准备 解析三个统称为连接

JVM规范严格规定了类初始化的情况 

    1) 遇到了new(new 实例化对象)、getstatic(读取一个静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外))、putstatic(设置静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外))、invokestatic(调用静态方法)这四条字节码指令,如果类没有初始化 则初始化。

    2)使用反射调用时,如果类没加载则先加载

    3)初始化一个类时,如果父类未初始化则先初始化父类。

    4)JVM启动时 先加载 执行主类 带main的那个

    5)MethodHandle 解析到静态(REF_getStatic ,REF_putStatic,REF_invokeStatic)的方法句柄 ,当主类未加载时加载。

以上行为称对一个类进行主动引用, 所有引用类的方式都不会触发初始化,称为被动引用。

public class SuperClass {
    static{
        System.out.println("superclass");
    }
    public static int value = 100;
    public final static int c = 1;
}
public class SubClass extends SuperClass{
    static{
        System.out.printf("Subclass");
    }
}
public class mainClass {
    public static void main(String[] args) {
        System.out.println("mainClass.main"+SubClass.value);
        SubClass[] s = new SubClass()[10];
        System.out.println(SuperClass.c);
    }
}

通过子类引用父类的静态字段,不会导致字累初始化。

使用数组来定义引用类不会触发该类的初始化,但是该操作扔实例化了一个类,[Llong.fenixsoft.classloading.SubClass 该类代表一个元素类型为SubClass的一位数组。

常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到类,因此不会触发类的初始化。

 

 

类加载的过程 

 

   加载

 

   过程 1通过全限定名(com.halo.xx)获取对应类的字节码。

 

            2将字节码中的静态存储结构转化为方法区运行时数据结构。

 

            3内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口

 

    从刚才也可以看到。数组的初始化是异于普通类的,其加载也是一样。因为数组类本身不通过类加载器创建,而是由JVM直接创建。但数组类和类加载器仍有很大的关系,因为数组类的元素类型是要靠类加载器去创建的,数组类C创建过程遵循以下规则:

 

            1如果数组的组件类型(component type 数组去掉一个维度的类型)是引用类型,那就递归加载过程,去加载这个组件类型,数组C将在加载该组件类型的类加载器的类名称空间上被标识。

 

            2如果组件类型不是引用类型(int[] 基本类型数组),JVM会吧数组C标记为与引导类加载器关联。

 

            3数组类的可见性与组件类型可见性一致。

 

            加载完成后,字节流就按照格式存储在方法区内。然后在内存中实例化一个java.lang.Class类存放在方法区,这个对象将作为程序访问方法区中的这些类型数据的外部接口。

 

    验证

 

     验证主要分4个阶段 :

 

           1 文件格式验证:该阶段的目的是保证输入的字节流能正确解析并存储于方法区内,格式上符合描述一个java类型信息的要求。这阶段的验证是基于二进制字节流进行的,只有通过了这个验证字节流才会在方法区内存储。后面的三项验证都是基于方法区的存储结构进行,不在操作字节流。

 

                文件格式验证的部分检查点: 是否以魔数开头0xCAFEBABE、主次版本号是否在当前虚拟机处理范围内、常量池的常量是否有不被支持的常量类型、指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量、class 文件个部分及文件本身是否有被删除或附加的其他信息。

 

            2 元数据信息:主要目的 对类的验数据信息进行语义校验,保证不存在不符合java语言规范的元数据信息。(数据类型校验)

 

                部分检查点:这个类是否有父类、是否继承了不允许被继承的类(final 类)、如果不是抽象类,是否实现了父类或接口所要求实现的方法、

 

            3 字节码校验 该阶段是整个验证过程中最复杂的阶段,主要目的是通过数据流和控制流分析,确定程序语义合法(方法体校验)

 

                部分检查点:保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现在操作栈里存了int,使用却用long类型加载入本地变量表、保证跳转指令不会跳转到方法体以外的字节码指令上、

 

           JDK1.6以后的javac和jvm进行了优化,给方法体的code属性的属性表中增加了一项名为“stackMapTable”的属性,这项属性描述了方法体中所有的基本块(basic block,按照控制流拆分的代码块)开始时本地变量表和操作栈应有的状态,在字节码验证 直接验证stackMapTable 的属性,将字节码验证的类型推导转变为类型检查 ,节省大量时间。

 

            4 符号引用校验(符号引用转化直接引用验证) 目的 确保解析动作正常进行,如果无法通过符号验证 则抛出 java.lang.IncompatibleClassChangeError异常的子类 -> IllegalAccessError、NoSuchFieldError、NoSuchMethodError     -Xverify:none 参数关闭类验证 

                 检查点: 符号引用中通过字符串描述的全限定名能否找到对应的类、指定类中是否存在符合方法的字段描述以及简单名称所描述的方法和字段、符号引用的类,方法,字段的访问性(public)是否可以被当前类访问。                      

    准备

    准备是为类变量(static修饰)分配内存并设置类变量初始值阶段(数据对象的零值)(类变量在初始化阶段才会被赋值)(如果字段被final修饰,也就是常量,在编译时javac会为value生成ConstantValue属性,在准备阶段就会根据ConstantValue对变量进行赋值,而不是零值),这些变量所使用的内存都在方法区分配,不包括实例变量(实例变量在对象实例化时随对象一起分配在java堆)

 

    解析

        是虚拟机将常量池内符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行,

            类或接口解析:假设当前代码所处的类为D,如果把未解析过的符号引用N解析为一个类或接口C的直接引用 1):如果C不是一个数组类型,JVM会把代表N的全限定名传递给D的类加载器去加载类C,在加载过程中,由于元数据验证,字节码验证的需要,又可能触发其他相关类的加载动作,例如加载类C的父类,一旦加载过程出现异常,则解析失败 。 2):如果C是一个数组,并且数组的元素类型为对象,按照1的规则加载元素类型,如果是基本类型的包装类(Integer),则由JVM生成一个代表这个数组维度和元素的数据对象。3)C生成一个有效的类或接口,在进行符号引用验证,确认D对C的访问权限,如果没有权限则抛出异常IllegalAccessError。

            字段解析:1先通过class_index项索引CONSTANT_Class_Info对字段所属的类或接口的符号引用解析,将字段所属的类或接口用C表示,2如果C本身包含了简单名称和字段描述符都与目标相匹配的字段,则返回字段的直接引用,3如果C中实现了接口,按照继承关系从下往上递归搜索接口中是否包含简单名称和字段描述符匹配的字段,4然后在搜索父类,5如果没有一次匹配成功则查找失败。6 如果查找过程中成功返回引用则对字段进行权限验证。

            类方法解析:1 2 4 3 5 6

            接口方法解析:1 2 3 5

    初始化

    在准备阶段,变量被赋为零值,在初始化阶段,就是执行类构造器<clinit>方法的过程

    <clinit>是编译器自动收集类中的所有类变量(static修饰)的赋值动作和静态语句块中的语句合并产生,顺序由源文件生成的顺序决定,静态语句块只能访问到定义在语句块之前的类变量,但是可以赋值。(非法向前引用)。 父类的<clinit>先执行。 <clinit>方法对于类或接口来说不是必须的。JVM会保证一个类的<clinit>方法在多线程环境中被正确的加锁同步,如果一个类的<clinit>耗时很长则会造成多个线程的阻塞。

 

类加载器

    对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机的唯一性,每一个类加载器,都拥有一个独立的类名称空间。两个类相等,必须类加载器是同一个加载器才有比较意义。如下代码:

 

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try{
                    String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
                    InputStream stream = getClass().getResourceAsStream(fileName);
                    if(stream == null){
                       return super.loadClass(fileName); 
                    }
                    byte[] bytes = new byte[stream.available()];
                    stream.read(bytes);

                    return defineClass(name,bytes,0,bytes.length);
                }catch (IOException o){
                    throw new ClassNotFoundException();
                }
            }
        };

            Object o = myLoader.loadClass("cn.chinaunicom.halo.ClassLoaderTest").newInstance();
            System.out.println(o.getClass());
            System.out.println(o instanceof cn.chinaunicom.halo.ClassLoaderTest);
     
    }
}

运行结果:

class cn.chinaunicom.halo.ClassLoaderTest

false

双亲委派模型

启动类加载器 (Bootstrap ClassLoader)这个类加载器使用C++实现 (hotspot),负责将存放在javahome\lib目录中,或者被Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的。启动类加载器无法被java程序直接引用。

普通类加载器:由java实现,独立于虚拟机外部 全部继承java.lang.ClassLoader。其中包含扩展类加载器和应用程序加载器,

扩展类加载器负责加载javahome\lib\ext目录中的,或者被java.ext.dirs系统变量所指定路径的所有类库,开发者可使用,

应用程序加载器负责加载用户类路径(classpath)上所指定的文件 一般是程序中默认的类加载器。

双亲委派模型

20160506184936657.jpg

 

© 著作权归作者所有

共有 人打赏支持
H
粉丝 0
博文 29
码字总数 36527
作品 0
青岛
程序员
私信 提问

暂无文章

精品书籍推荐

JavaScript书籍推荐 1、[JavaScript高级程序设计(第3版)] 2、你不知道的JavaScript(中卷) 3、ES6标准入门(第二版)阮一峰

轻轻的往前走
17分钟前
4
0
JVM(六)为什么新生代有两个Survivor分区?

本文会使用排除法的手段,来讲解新生代的区域划分,从而让读者能够更清晰的理解分代回收器的原理,在开始之前我们先来整体认识一下分代收集器。 分代收集器会把内存空间分为:老生代和新生代...

王磊的博客
23分钟前
6
0
程序员最喜欢的15款文本编辑器推荐

程序员最喜欢的15款文本编辑器推荐 2017年09月18日 17:30:50 kangle_zhu 阅读数:59390 转载地址:http://www.cr173.com/html/50553_1.html 很多时候比如编程查看代码或者打开各种文档下我们...

linjin200
25分钟前
9
0
如何在php后端及时推送消息给客户端

walkor大神,目前需求是这样的: 有一群商家在后台网页处理批量导入产品 -》 服务器接受请求 -》 开始foreach一个一个处理导入请求; 我现在想每成功导入一个就推送到前台显示已经导入成功,...

dragon_tech
43分钟前
16
0
Java利用hanlp完成语句相似度分析的案例详解

分享一篇hanlp分词工具使用的小案例,即利用hanlp分词工具分析两个中文语句的相似度的案例。供大家一起学习参考! 在做考试系统需求时,后台题库系统提供录入题目的功能。在录入题目的时候,...

左手的倒影
50分钟前
19
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部