分享

java类加载的详细过程

 2017helloworld 2017-07-28

概述

虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行验证,准备,解析,初始化的一个过程,最终是可以被虚拟机直接使用的java类型,这即是类加载的一个简略的过程。

Java中的类加载是在运行时加载,这样会比较的消耗性能,可是恰是在运行时加载使得java具有极好的灵活性和可扩展性。

类加载的时机

类从被加载到内存中开始,到卸载出内存为止。它的生命周期总共七个阶段:加载---->验证---->准备---->解析---->初始化---->使用---->卸载。如下图所示:java类加载的详细过程

类的生命周期

其间解析这个进程是不确定的,它可能会在初始化后,这是为了使java支持运行时的绑定。

·*new ,getstatic,putstatic,invokestatic这四条指令时会触发初始化的操作。

new是new一个新的对象时会触发初始化。

ogetstatic是获取静态字段时会触发。

oputstatic是设置静态字段时会触发。

invokestatic是调用另一个类的静态方法的时候会触发。

PS:需要注意的是getstatic和putstatic被final修饰的,在编译期就放入到常量池中是不会触发的。

·*使用java.lang.reflect的包方法对类进行反射调用时,如果类没有初始化就需要进行初始的操作。

·*子类进行初始化时需要对父类先进行初始。

*·java启动时需要的启动主类,程序的入口。该类就需要进行初始化。

*使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.Methondhandle实例最后解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,如果没有进行初始化时会触发初始化。

加载

加载是类加载中前面提到的其中的一个过程。类加载的基本过程:

·*通过全限定类加载二进制流。

·*将二进制流代表的静态存储结构转换方法区中运行时的数据结构。

·*在内存中生成java.lang.Class对象,将这个作为该方法区这个类中各种数据的一个入口。

加载分为数组类加载过程和非数组类的加载过程。java的数组类的加载过程其实是由虚拟机直接加载的但是数组中的类型需要类加载机制加载:

·*非数组类加载机制:可控性强既可以由系统类加载器进行加载又可以由用户自定义的类加载器进行加载。

·*数组类型的加载机制:数组类型的加载机制如果是引用类型,就使用递归进行加载,并且会在加载的类型上加入一个标志。如果是非引用类型则会把标志与引导类加载器关联。

ps:数组类的可见性与它组件的可见性是相同的,如果组件类型不是引用类型的可见性一般设置为public。

类加载完成会有一个连接,可能在没完成加载就开始连接,虽然如此但是该顺序是一定的。

验证

验证的主要目的是保证加载进来的Class文件的字节流包含的信息符合虚拟机的当前的要求,不会有危害自身的数据存在。

Java是相对C++语言是安全的语言,例如它有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的四个阶段文件格式验证-->元数据验证-->字节码验证-->符号引用验证。

·*文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。

是否以魔数开头。

主,次版本号是否在当前虚拟机处理的范围之内。

常量池中是否有不被支持的常量类型。

指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。

CONSTANT_Utf8_info型的常量中有不符合utf8格式的编码数据。

还有其他的验证这里就不一一的列举了。

·*元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。

·*字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。

·*符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。

尽管验证很主要但并不是必须的阶段。当然大量重复的验证会相当的花费性能和时间的。

准备

准备阶段主要是类变量进行分配内存和数据的初始化阶段,所谓的初始化并不是你编码时所定义的变量值。例如:

java类加载的详细过程

数据的初始化并不会将它初始化为20,而是初始化为0,系统有一套自己的初始化值。如下图:java类加载的详细过程

当然会有特殊的情况,如下面的代码:java类加载的详细过程

这种情况是类的字段时存在ConstantValue属性所指定的字段。用final修饰后出现该属性,加初始化时会直接的使用ConstantValue的属性值,所以会初始化为20。java类加载的详细过程

解析

解析是将常量池中的符号引用转化为直接引用的过程,还记得前面验证阶段时出现的符号引用验证吗?就是对该阶段的验证。

·*符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。

·*直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的而布局有关的,并且一定加载进来的。

虚拟机可能会多次的进行解析。解析主要的对类,接口,字段,类方法,接口方法,方法类型,方法句柄和调用点限定符引用进行。这七种解析有细节上的不同,主要的思想是通过限定性类名找到解析的类型进行解析。主要的是会分为数组类型,非数组类型存在一个直接进行解析的过程。

初始化

初始化算是类加载过程的最后一个阶段,在这个阶段在是真正的开始有java代码主导。大家应该记得在准备阶段已经进行过一次赋值,但是只是系统的默认赋值(ConstantValue的例外情况)。初始化是执行的过程。

·*的主要是查找static模块,用户自定义类变量的赋值,该顺序是由文件中的顺序界定的。加载过程存在的是父类的一定会比子类先进行加载到,因为会保证子类的加载完成时父类的一定会加载完成。所有就像大家所知道的java.lang.object一定会是虚拟机中第一个加载完成的。

·*在接口中的加载是不同的它是不存在静态块的,接口中也是会有赋值进行的,但是接口中的是在需要用到才会去进行加载的。

·*允许在定义之前进行赋值的操作,但是不允许使用,如下:java类加载的详细过程

*虚拟机会保证在多线程的环境下进行加锁,保证正确执行。如果有多个进行加载一个会保证只有一个去加载,其他的会进去阻塞等待中。同一个类只会加载一次,就算多个进入阻塞也不会重新唤醒。

类加载器

·*类与类加载器:一个类的相同判断条件大家都知道,但是如果不是由同一个类加载器加载出来的,就算是看起来相同的也是出现false的。

·*三大类加载器:

启动类加载器

扩展类加载器

应用程序类加载器

·

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多