JVM类加载机制

栏目: Java · 发布时间: 8年前

内容简介:JVM类加载机制

先说class文件

编写的 java 代码首先被编译成class二进制文件,这是实现平台无关性的关键一步。至于class文件里面的具体内容,可以用编辑器打开,结合一些教程一项一项的分析。

其实,我主要想说的是,一个class文件代表一个类型(类或者接口),也可以理解为元数据。在我们的程序中访问一个类型的元数据,并做点什么,比如反射调用等,是很有意义的。

类加载

过程:通过类(或接口)的全限定名找到对应的class文件,读取里面的内容,在方法区上分配运行时数据结构,并返回一个java.lang.Class的对象(不一定在堆中),代表这个类型和访问方法区中的类型数据。

类装载器:每个类装载器都有自己的命名空间,装载特定区域的类型,实现类型加载。系统提供了3个类加载器:启动类加载器(Bootstrap ClassLoader):由虚拟机实现,加载JAVA_HOME/lib目录下以及-Xbootclasspath参数所指定的路径下的可识别的类库;扩展类加载器(Extension ClassLoader):加载JAVA_HOME/lib/ext目录下以及java.ext.dirs系统变量所指定的路径下的类库;应用程序类加载器:(Application ClassLoader):加载classpath里面的类。当然,我们还可以自定义类加载器。

双亲委派:除了启动类加载器,其余的类加载器都必须要有父类加载器。这里的父子关系不是继承的父子关系,而是通过组合来实现复用。当一个类加载器收到类加载的请求时,它先是递归的向父类加载器委派这个请求,直至启动类加载器,只有当父类加载器无法完成加载的时候,子类加载器才会去加载这个类。思考一下,为什么要这样设计呢?所有类的老祖宗是Object,如果我们自己写了一个恶意的Object类,并且自己加载进来,那就会造成混乱。可是有了双亲委派,就有了优先级,所以最终只有一个java.lang.Object类提供服务。

自定义类加载器:通过ClassLoader的源码可以看到,loadClass方法实现了双亲委派的逻辑,并最终会调用findClass方法去完成主要工作。所以在自定义类加载器的时候,如果想突破双亲委派模型,可以重写loadClass方法,否则重写findClass方法即可。在findClass方法里面,我们可以先通过类名获得该类文件的二进制流,然后调用defineClass方法去完成类加载的工作。这里的defineClass方法由虚拟机实现,我们不用管,而且它是final和protected,所以我们只管继承,然后调用。

突破双亲委派:先回想一下jdbc的工作模式。sun公司首先制定一套标准,也就是一大推接口,然后各个数据库厂商去写自己的实现。当我们的程序要用的时候,就把相应的数据库的驱动jar包导入classpath。这里就会产生一个问题。sun公司的接口肯定放在基础类库里面,由启动类加载器加载,但是这些接口里面会用到第三方厂商提供的具体实现类,然后就需要去加载。如果按照双亲委派模型,启动类加载器是不能去classpath里面加载类的。这是一个硬性的逻辑阻碍。于是,那些Java大神设计出了一个线程上下文类加载器(Thread Context ClassLoader)。如果创建线程的时候没有设置,将会从父线程继承过来,如果整个程序都没有设置的话,默认是Application ClassLoader。所以在一个线程中,当用Bootstrap ClassLoader去加载基础类库的时候,可以用线程上下文类加载器去加载其他的类。

tips:ClassLoader的loadClass方法和Class的forName方法都能加载类,区别是,loadClass只加载类型,而forName不但会加载类型,默认还会最终初始化类型。

说说剩下的阶段:验证、准备、解析、初始化。需要注意的是,这些阶段(解析除外)只是按照这个顺序开始,但是执行的过程中可能存在交叉。

验证:就是要对加载的二进制流文件进行各种检查,很好理解。

准备:为类变量(static)分配内存并设置初始值,即所谓的"零值",但是不包括常量(final)。

解析:将常量池的符号引用替换成直接引用,这个阶段发生时间没有明确规定,但是有具体限制:在符号引用被使用之前,必须被解析。

上述3个阶段合称连接阶段。

初始化:这里是类型初始化,不是对象初始化。

对于第一个阶段--加载,没有明确规定时机,但是初始化阶段有且仅有明确的的4种情况:

1、访问类型的静态成员(final常量除外)和使用new关键字

2、反射调用

3、一个类型的父类型先初始化

4、包含main方法的主类

初始化的过程:编译器自动按顺序收集类变量赋值语句和静态语句块(static{})生成<clinit>()方法,如果一个类型没有类变量赋值以及静态语句块,就不会自动生成。JVM需要保证调用子类的<clinit>()方法前先调用父类的<clinit>()方法(接口不必),同时保证线程安全。

最后,说一个特例,数组类。数组类由JVM自动生成,自动创建。假设自定义类com.fbi.A,A[] arrs = new A[10];语句,JVM会生成"[Lcom.fbi.A"这样的一个类型。这不是重点,真正的重点是这条语句只会去加载类A,但不会初始化类A。

类加载与动态代理

动态代理

所谓动态,就是在运行期间生成代理类。不然,有100个需要被代理的类,你就得手动写100个代理类,代码膨胀得厉害。

而我现在的目标是弄清楚jdk如何实现动态代理。

阅读Proxy类的源码能够看清大体流程:

1、我们自己提供接口和类加载器,然后jdk去通过Class.forName的方式去加载以及初始化这些接口,并生成类型信息。

2、有了这些接口的类型信息,就可以通过反射得到所有的方法的信息

3、这个时候有2种选择:通过已有的信息生成代理类的java源代码文件,然后动态编译生成class文件。

而jdk用的是另一种,将已有信息直接写入class文件。因为class文件的内容分布是固定的,所以按照class文件的格式一个一个的写二进制流就可以实现。

相比第一种,第二种的效率更高。

4、有了class文件,就可以调用defineClass方法生成代理类的类型信息

5、有了代理类的类型信息,就可以通过反射调用参构造方法,把我们自定义的InvocationHandler传进去,生成代理类的实例。

通过动态代理的实现原理,可以清楚的看到:类加载机制相对灵活,只要你能得到符合规范的class文件,就可以生成对应的类型信息,然后通过反射就可以干很多事情。

但是动态代理的唯一遗憾是必须要实现接口,而另外还有一种方式---cglib,可以更加灵活的实现动态代理。

本文永久更新链接地址 http://www.linuxidc.com/Linux/2017-06/144457.htm


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Building Websites with Joomla!

Building Websites with Joomla!

H Graf / Packt Publishing / 2006-01-20 / USD 44.99

This book is a fast paced tutorial to creating a website using Joomla!. If you've never used Joomla!, or even any web content management system before, then this book will walk you through each step i......一起来看看 《Building Websites with Joomla!》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具