JVM类加载机制(ClassLoader)源码解析

http://blog.csdn.net/chenyi8888/article/details/7066569

其实JVM类加载机制,简单地说就是类管理,也就是我们生成的class文件。
三个步骤:装载(load)、链接(link)、解析(Resolve)、还有初始化(Initialize)
关于网上有很多讲解加载的方式,和调用的方式,还是几个基本的classLoader,这里就不在多描述了。
这里更多的是从源码上来讲解,达到理论结合实际。

首先是ClassLoader这个抽象类,这个是实现自定义类的基础。那么在调用的时候,首先都是调用loadClass这个方法,如图:

该方法调用的是一个重载方法loadClass(name,false)方法如图:

第一行注释很清楚的表示,从当前的类加载器里加载class文件,如果已经加载,就直接返回;不存在就加载;如果返回NULL,表示class文件不在这个类加载器的范围内。
这是就会执行if(c==null)下面的方法,就会去找父加载器(类加载器的整个结构是树形的,这里不多介绍),如果父加载器不存在,就直接找到根加载器(根加载器一定存在)。
我们现在开始解析每个被调用方法。
首先看下findLoadedClass方法,如图:

该方法首先,校验类名是否正确(类似于 test.demo.Test.class),校验成功后,调用本地方法findLoadedClass0方法。

接着是parent.loadClass(name,false)方法,该方法是重复的,只是对象不同而已,就不介绍了。
接着是findBootstrapClassOrNull方法,如图:

方式与findLoadedClass方法一样,就是本地调用是findBootstrapClass方法。
如果此时类还不存在,有一个findClass方法的调用。代码如图:

这里看上去觉得很奇怪,直接就抛一个空异常了,其实根据注释所说,就是自定义的类加载器需要实现这种方法。
为什么要实现这个方法呢?原因就是有些场景会需要通过网络传输的方式进行加载类(该类其实是在远程服务器上的),在很多地方就会用到,例如远程调用等技术。
具体的实现例子,在URLClassLoader里就有体现,这里暂不介绍。

最后是调用了resolveClass方法,代码如图:

这个方法很简单,主要是重新进行解析类,该解析主要是对所有的属性/方法调用是否存在、相应的权限(如private、public等)进行验证。那么为什么默认都是传入false呢?
那是因为是否需要更强的安全机制的检测,
另一个情况是类加载是无序的,会导致类链接不成功。
这里有个状态位的控制,主要是可能有些场景需要更严格的对类进行验证(目前我还没有使用到这样的场景)。

这里要注意的是:
类为什么是树形结构,主要就是安全,因为类加载器都是从不同的目录进行加载的(网上有介绍三个最基础的类加载器Bootstrap ClassLoader\Extension ClassLoader\System ClassLoader加载的默认目录,这里不多介绍),所以用这种目录的方式来进行权限管理,常用都是使用classpath系统变量,如果我想自己定义一个加载目录,那么就需要实现自己的类加载器,进行相应的权限管理。
另外这个抽象类,实现了只是从本地进行加载类的方式,如果需要进行远程加载类,那么也需要实现自己的类加载器。

这里是类加载器的基本功能介绍,里面还有如何加载native Library 方法、class文件/包名相关安全验证等,在后续会继续介绍。

 

 

JVM类加载机制(ClassLoader)源码解析(2)

分类: java服务器 4726人阅读 评论(0) 收藏 举报

我们来对defineClass这个方法进行解析,该方法比较复杂,首先如图:

该方法主要是通过一个字节的数组,对该数据进行解析、验证、二进制码格式校验。通过抛出的ClassFormatError的这个异常来看,也是验证这个类的格式是否符合JVM的规范。
最终是将一个字节数组转换成类实例(注意不是实例对象,是有区别的)。
目前该方法已被替代,注意上图里的注释。

目前新方法,如下图:

第一个参数,其实就是类名(包括整个包名在内)。
现在对该方法进行详细的解析,首先看下check方法,如图:

非常简单,就是检测classLoader是否初始化。

preDefineClass方法,如图:

checkName检测类名是否有效(去除数组类型的判断),禁止以java.为前缀包名。如果没有保护域,则获取默认保护域;最后是进行数字签名的验证。
主要看下数字验证方法,checkCerts方法,如图:

数字验证的方式也很简单,如果存在就比较当前保护域里的数字签名与之前的数字签名是否匹配;反之就在package2certs对象(就是一个hashtable)里缓存起来。
这里的数字签名是从CodeSource这个类型的对象来获取,也就是代码源里的签名。
compareCerts方法就是对比两个数字签名是否匹配(方法很简单,这里就不用截图了)。这里主要是针对于类安全考虑,防止有人恶意修改类文件,采用的是通用的java安全框架来实现的。以后会在java安全的源码分析里在进行详细介绍。

defineClassSourceLocation方法,如图:

非常简单,就是获取代码基的路径。
然后就是开始调用本地解析方法defineClass1进行处理(类似于findLoadedClass方法处理)。如果抛出ClassFormatError异常,在给一次机会(调用方法defineTransformedClass),重新传输一次类的二进制码做解析;如果还是失败,就抛出ClassFormatError异常类型。

最后是执行postDefineClass方法,如图:

这里就是用之前比较过的数字签名,对类进行签名操作(也就是类似于加密操作)

这里需要注意的是:
defineClass方法与loadClass方法是两种不同加载类的实现。
区别在于:
前者是有严格的安全机制,输入源是一个二进制码;后者很简单就是一个本机的加载方式,输入源来自本地存放的class文件。
前者比较适合于网络远程加载类,因为需要进行安全控制;后者是一个比较基于自定义目录加载的实现。
前者在实现时主要是findClass方法与之配合使用;而后者主要是要设置加载的URL(也就是路径)。
这里非常明显地体现出了java的核心思想(严格地来讲应该是核心特性),安全和网络。

 

JVM类加载机制(ClassLoader)源码解析(3)

分类: java服务器 4743人阅读 评论(0) 收藏 举报

java的类加载器,还有一个特殊的功能,就是加载本地库。这个功能是与关键字native是有关系的。简单地说就是调用C++/C的本地库(windows是后缀为.dll,linux下是后缀为.so)。

调用的地方是使用System这个类,其中有两个方法如下:
load(String filename)
loadLibrary(String libname)
一个是根据文件名,一个根据lib名。注意文件名不等同于lib名,不然写错了,会报加载失败信息。
System类是一个很特殊的类,与底层交互较多,会看到很多native关键字,到时候在会在解析System和Runtime时再详细介绍。

回到之前的主题,这两个方法最终都是调用到了ClassLoader.loadLibrary(fromClass, name,isAbsolute)。该方法如图:


说明下参数:
fromClass这个参数是当前调用者的类实例,例如:
public class Test{
            public void load(){
                   System.loadLibrary("1232");
            }
}
这时fromClass参数就是Class<Test>。
isAbsolute参数很简单,指是否是规则路径(例如:../../test.java)。
name参数有可能是文件名方式,也有可能是libname方式。

该方法第一阶段就是基于DownloadManager判断,如果JRE没有完成并且当前还有线程下载时,就会调用DownloadManager类的静态方法downloadFile。
DownloadManager类,这个属于sun.jkernel包下的,与JRE组件有关,简单地说就是安装了JRE的存放路。
有时候JRE是在嵌入的位置就是JAVA_HOME/jre,有时候是独立位置,特别是在windows操作系统下安装时,会提示安装JRE这时可以随时选择存放路径。
JVM初始化完成后会调用此类,另外DownloadManager这个类也支持命令行的操作。以后会进行更多的详细介绍。

因为较为复杂,很多用了调用本地方法,所以我这里只简单介绍。
java.library.path是java的系统变量,其值等于系统变量里的PATH。
sun.boot.library.path是java系统变量,其值等于JAVA_HOME/jre/bin。

主要是通过4个过程来加载本地库。
第1步骤就是判断是否是规则路径,为true,就直接对规则路径进行转换后加载,如果加载失败直接抛出异常并执行步骤5;反之执行步骤2。
                规则路径就是使用了.或..这样符号。
第2步骤如果类加载器不为空,从类加载里获取libname信息,
               但是抽象类ClassLoader是一直返回NULL的(可查看ClassLoader.findLibrary方法),所以基本上都会直接执行步骤3(除非是自定义类加载器,重写了此方法)。
第3步骤首先从sun.boot.library.path里的全部路径下搜索libname进行加载,如果成功执行步骤5;反之执行步骤4。
第4步骤首先判断类加载器是否为空,不为空时,将从java.library.path里的全部路径下搜索libname进行加载,不管成功与否,都会执行步骤5。
第5步骤执行完毕。

   另外本地库名称(已经加载过的)在代码中有三个存放属性,如下:
    // All native library names we've loaded.
    private static Vector loadedLibraryNames = new Vector();
    // Native libraries belonging to system classes.
    private static Vector systemNativeLibraries = new Vector();
    // Native libraries associated with the class loader.
    private Vector nativeLibraries = new Vector();


加载之后存放相关信息使用NativeLibrary类来保存的,该加载机制与JNI技术有密切联系,也使用下JNI会对该加载功能有更深入的了解。

 

System与Runtime源码解析

分类: java服务器 4095人阅读 评论(0) 收藏 举报

在类加载器里提到了System与Runtime类,这里就趁热打铁来对这两个源码进行解析,因为System与Runtime关联很紧密,所以就一起来解析吧。
首先来看看System类提供的几个特性:
1、standard input, standard output, and error output streams
2、访问扩展属性和java的环境变量
3、加载本地内库
4、提供一个arraycopy的复制功能
5、获取Console对象
6、获取和设置SecurityManager对象
7、获取本地库文件mapLibraryName方法
      该方法示例:System.out.println(System.mapLibraryName("awt")); 打印结果为awt.dll,这个文件存放的路径在JAVA_HOME/jre/bin目录下,大家可以自己试试其他的。
8、JVM退出(exit)

看下Runtime类提供的几个特性:
1、一个JVM对应一个Runtime对象(single)
2、允许访问和调用其他应用程序
3、扩展ShutdownHook
4、获取内存使用相关信息
5、加载本地内库
6、JVM退出(exit)

首先分析下System类,该类有个重要的方法,如下图:

该方法是被JVM调用的,很奇怪吧。当时我也没想明白,这个方法是private,怎么被调用到呢?后来仔细分析后发现,注意这个方法,如下:
private static native void registerNatives();
    static {
        registerNatives();
    }
该方法会将initializeSystemClass方法映射到本地方法里,方便JVM调用;这里很重要,JNI方便java去调用C++/C的动态连接库。而该方法是让C++/C能调用到java方法。
至于registerNatives方法做了什么更具体的事情,可以去查看源码。
我们来具体分析下initalizeSystemClass做那些事情:
1、初始化out、in、err等流
2、初始化环境变量Properties
3、初始化信号量,Terminator.setup();
4、sun.misc.VM.initializeOSEnvironment();
       
       OSEnvironment代码如下:
       
       这里就很明显了,主要是设置些错误模式标识,JVM如何处理这样的错误(目前是四种:临界区错误、文件错误、自动修复内存对齐、一般的故障保护)。
       
5、sun.misc.VM.maxDirectMemory();
       该方法在VM很简单,就是直接return directMemory,该参数的设置与-XX:MaxDirectMemorySize=<size>有关
6、sun.misc.VM.allowArraySyntax();
       在VM也很简单,也是直接返回return allowArraySyntax
7、sun.misc.VM.booted();
       在VM里是将booted赋值为true。

个人认为5、6两个步骤是没有必要的,不知道为什么要调用下。
另外就是如何验证initalizeSystemClass是JVM调用的,很简单,可以参考下out/in/err等属性是如何初始化的。我自己写了模拟例子,如下:
class AB{
}

final class AB_System{
public static final AB a=nullAB();

private static AB nullAB(){
if (System.currentTimeMillis() > 0) {
   return null;
}
throw new NullPointerException();
}

static{
System.out.println("ab_system static");
}
private AB_System(){
}
public static void print(){
System.out.println("abaddd_dadfa");
}
}
public class Test_System {
public static void main(String[] args) {
System.out.println(AB_System.a==null);
}
}
运行结果是true。

再来分析下Runtime类。
因源码里有较多的native方法,所以逻辑比较简单,
这里就是主要是exec这样的方法,可以找些例子操作体验下,就行了。

与之相关的时Process类,这个类就是外部被调用的应用程序,在java里的一个代理对象(也可以叫做交互的接口对象,例如返回相关的运行状态等),后面会对这个类进行解析。

还有就是掌握强制终止和正常终止的区别。

关于两者之间的关联
个人认为差别不是很大,因为有很多相同点(System里也有需要调用Runtime里的方法)。
分成两个类,还是因为职责分开吧,System比较倾向于程序员使用(是一个工具类,不能被实例化),而Runtime(是一个single object)更倾向于与JVM和其他应用程序交互。
这两个类主要用于的场景如下:
如何安全的关闭应用程序(换句话说就是java程序退出或者JVM退出),可能很少在写java程序时(特别是java的开源项目众多),要考虑如何关闭JVM的
自定义的SecurityManager类
增加Hook
加载自己的动态链接库等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值