JVM(1) : Java类的加载机制

来源:互联网 发布:初中优化测试卷答案 编辑:程序博客网 时间:2024/06/06 02:21

PS : 本文乃学习整理参考而来。文中方法区、常量池等将在 JVM(2)内存结构中解释;

要点概括: 本文主要对类加载、初始化及静态加载与动态加载做主要记录

  • 概诉
  • 类加载总结
  • Java反射之静态加载与动态加载

JDK1.5以后,Oracle采用HotSpot VM, HotSpot 及相关主流Jvm详细讲解:
- [ Java程序员必学的Hotspot JVM选项]


什么是类加载机制?

概诉

            虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。


类加载总结

下图是Java给出的运行流程来帮助我们理解:

这里写图片描述是

上图中有两个部分,Compile部分和Runtime部分

Compile :
             上图编译阶段将目Hello.java文件编译成Hello.class文件,供虚拟机加载使用(加载是什么后文解释)。为什么要编译,原因很简单,虚拟机只认识.class文件, 这也是java跨平台的基础,所谓一次编译,到处执行。参考:
[ Java跨平台性]

Runtime :
             Java类的生命周期从这里开始。类的生命周期包括以下几个阶段:

这里写图片描述

其中类加载过程为黄色区域,包含:加载连接初始化,其中连接包含:验证准备解析


加载: 前人总结为以下三个步骤

1、通过一个类的全限定名来获取其定义的二进制字节流。2、将这个字节流所代表的静态存储结构转化为"方法区"的运行时数据结构。3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

个人简结: 步骤1中,JVM通过类全限定名获取其class文件,而获取的过程通过ClassLoader(类加载器)完成。在Hotspot中,可将类加载器大致分为以下三类(其中个人认为应用程序类加载器是一般程序员关心的):

启动类加载器 (Bootstrap ClassLoader):它使用C++实现,其他加载器使用Java实现,负责加载存放在JDK\jre\lib下的jvm运行时必须的Jar包(charsets.jar、rt.jar(Object类就在其中)、resources.jar等等)。

扩展类加载器 (Extension ClassLoader) : 负责加载JDK\jre\lib\ext目录中的运行时必须的jar包。

应用程序类加载器:(Application ClassLoader):负责加载用户类路径(ClassPath)所指定的类。 我们写的代码就由此类的实现类加载。

此外,加载器的加载机制为双亲委派模型,双亲委派模型个人理解为:当需要加载类时,先交给上级加载器尝试加载,如果未加载成功则自己加载。三类加载器的层级关系(启动类 > 扩展类加载器 > 应用程序类)。简言之,当需要加载类时先由启动类加载器加载,如果未加载到该类则交给扩展类加载器,再到应用程序类加载器。如果都未成功加载则会抛出异常ClassNotFoundException(此理解不考虑已实现自定义类加载器)。 虽然后文代码片段通过getParent来获取上级加载器,但官方和许多前人解释双亲委派模型并不是一种继承实现。

这里写图片描述

以下部分代码出自博主关于ClassLoader的文章: [ 一看你就懂,超详细java中的ClassLoader详解]

public class ClassLoaderTest {     public static void main(String[] args) {        ClassLoader loader = Thread.currentThread().getContextClassLoader();        System.out.println(loader);        System.out.println(loader.getParent());        System.out.println(loader.getParent().getParent());    }}

输出结果

sun.misc.Launcher$AppClassLoader@64fef26asun.misc.Launcher$ExtClassLoader@1ddd40f3null

可以看见AppClassLoader.getParent()得到的是ExtClassLoader,而ExtClassLoader.getParent()得到的是null,null并不是表示他没有上级加载器,查询相关资料,因为Bootstrap ClassLoader(启动器类加载器)通过c++开发。

还有一种类加载器:自定义加载器,上文中应用程序类加载器负责加载ClassPath所指定的类。如果需要加载的类在远程服务器上,那么可以实现自己的的类加载器,通过网络实现远程加载。如JAVA官方给出的图中,从Network中获取class文件。这种情况个人理解为是对自定义加载器实现场景的一种描述。

这里写图片描述


验证:前人总结为:确保Class文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:

  • 文件格式验证验证字节流是否符合Class文件格式的规范。如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。

  • 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。

  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

  • 符号引用验证:确保解析动作能正确执行。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响。


准备:(引用前人总结)
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。

假设一个类变量的定义为:public static int value = 3;

那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,并未对类进行初始化。把value赋值为3的动作将在初始化阶段才会执行。


解析:(引用前人总结)把类中的符号引用转换为直接引用

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。


初始化:为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

  • 声明类变量是指定初始值。 如上public static int value = 3,此时value值才被赋值为3。
  • 使用静态代码块为类变量指定初始值

静态代码块的执行时机:类第一次被初始化时,执行静态代码块

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如Class.forName(“ClassName”))
  • 创建类的实例,也就是new的方式
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

结束生命周期:在如下几种情况下,Java虚拟机将结束生命周期

  • 执行了System.exit()方法
  • 程序正常执行结束
  • 程序在执行过程中遇到了异常或错误而异常终止
  • 由于操作系统出现错误而导致Java虚拟机进程终止

Java反射之静态加载与动态加载

静态加载:编译时就需要加载全部可能使用到的类,一个类出错,其他类的功能都不能得到执行,不允许未知程序出现。

先看如下代码,其中”Word”和”Excel”代表普通Java类。

部分代码出自
http://blog.csdn.net/briblue/article/details/54973413

 public static void main(String[] args) {        //静态加载。编译时加载,编译时就需要加载全部可能使用到的的类,一个类出错,其他类的功能都不能得到执行          if (true) {            Word Word = new Word();            System.out.println("Word");        }        if (false) {            Excel excel = new Excel();            System.out.println("Excel");        } }

如上逻辑,当程序运行时,尽管第二个if代码段不会执行,但是编译时,因为其可能会用到,Jvm仍会加载”Excel”类。假设此时”Excel”类不存在则会报出异常。从”以可配置为荣,以硬编码方式为耻“的角度,上述写法限制代码拓展性和可维护性。至于可配置性,先提下”Spring”的Ioc原理实现就是基于反射实现的。

Java反射机制概要 : Java反射是Java被视为动态语言的一个关键性质,允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息(作用域、属性、方法、子父类信息等所有)。反射机制容许程序在运行时加载、探知、使用编译期间完全未知的class文件(动态加载的实现关键)。而静态加载在编译期间不允许未知的程序存在。

动态加载:参考如下代码

假设Word、Excel类不存在,在编译期间,1、2 两行编译报错,存在未知程序,编译无法通过。而3、4两行并不会报错,因为不存在未知程序,当程序运行到此处时才会去加载 “Word.class” 和 “Excel.class” , 若此时加载不到,则抛出ClassNotFoundExcepition。以下重点:用过Sping的都知道IOC容器中的Bean的外部属性可以在配置文件中进行配置。此时第5行,对TestClass中的Office属性的具体实现可在配置文件中随意配置,随业务更改而更改,无需硬修改,实现配置为荣。这也是Java多态的表现。

public static void main(String[] args) {       Word testWord = new Word(); // 1       Excel testExcel = new Excel(); //2       Office Word = Class.forName("Word");  // 3       Office Excel = Class.forName("Excel");// 4       TestClass test = Class.forName("TestClass");// 5       test.print();   //输出的内容由配置决定; }public class Excel implements Office{      public void print(){          System.out.println("Excel");      }  }  public class Word implements Office{      public void print(){          System.out.println("Word");      }  } public class TestClass {      private Office office;    //省略getter setter;} 

参考 :

http://blog.csdn.net/briblue/article/details/54973413
https://www.zybuluo.com/jewes/note/57352
http://www.cnblogs.com/ityouknow/p/5603287.html
http://blog.csdn.net/jacxuan/article/details/53709357

原创粉丝点击