JVM加载class文件的原理
当Java编译器编译好.class文件之后,我们需要使用JVM来运行这个class文件。那么最开始的工作就是要把字节码从磁盘输入到内存中,这个过程我们叫做【加载 】。加载完成之后,我们就可以进行一系列的运行前准备工作了,比如: 为类静态变量开辟空间,将常量池存放在方法区内存中并实现常量池地址解析,初始化类静态变量等等。这篇文章我们要好好谈谈JVM是如何加载class文件的?
1、JVM加载类的过程
当我们使用命令来执行某一个Java程序(比如Test.class)的时候:java Test
(1) java.exe 会帮助我们找到 JRE ,接着找到位于 JRE 内部的 jvm.dll ,这才是真正的 Java 虚拟机器 , 最后加载动态库,激活 Java 虚拟机器。
(2) 虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类装载器 ―― Bootstrap Loader(启动类装载器 ) 。
(3) Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader(扩展类装载器) ,并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。
(4) 然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader(用户自定义类装载器 ) ,并设定其 Parent 为之前产生的 ExtClassLoader 实体。这两个加载器都是以静态类的形式存在的。
这里要请大家注意的是, Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。
初学者对这个过程很难理解,我们将在下面详细的讲讲类装载器和"Parent"是什么。
2、类装载器体系结构
JVM加载class文件必须通过一个叫做类装载器的程序,它的作用就是从磁盘文件中将要运行代码的字节码流加载进内存(JVM管理的方法区)中。下面是几个比较重要的概念:
(1) 启动类装载器 : 每个Java虚拟机实现都必须有一个启动类装载器。它只负责在系统类(核心Java API的class文件)的安装路径中查找要装入的类。这个装载器的实现由C++ 所撰写而成,是JVM实现的一部分。
(2) 扩展类装载器和自定义类装载器 : 负责除核心Java API以外的其它class文件的装载。例如、用于安装或下载标准扩展的class文件,在类路径中发现的类库的class文件,用于应用程序运行的class文件等等。这里有一点需要注意:自定义类装载器并非由应用程序员自己实现,它也是JVM
(3) 命名空间: Java虚拟机为每一个类装载器维护一个唯一标识的命名空间。一个Java程序可以多次装载具有同一个全限定名的多个类。 Java虚拟机要确定这"多个类"的唯一性,因此,当多个类 装载器都装载了同名的类时,为了唯一地标识这个类,还要在类名前加上装载该类的类装载器的标识(指出了类所位于的命名空间)。下图显示了两个类装载器有关的命名空间,显然,不同的类装载器允许装载相同的类Volcano。
命名空间有助于安全的实现,因为你可以有效地在装入了不同命名空间的类之间设置一个防护罩。在Java虚拟机中,在同一个命名空间内的类可以直接进行交 互,而不同的命名空间中的类甚至不能察觉彼此的存在,除非显式地提供了允许它们进行交互的机制。一旦加载后,如果一个恶意的类被赋予权限访问其他虚拟机加 载的当前类,它就可以潜在地知道一些它不应该知道的信息,或者干扰程序的正常运行。
3、双亲委托模型
用户自定义类装载器经常依赖其他类装载器——至少依赖于虚拟机启动时创建的启动类装载器—来帮助它实现一些类装载请求:.在版本1.2前,非启动类装载器 必须显式地求助于其他类装载器,类装载器可以请求另一个用户自定义的类装载器来装载一个类,这个请求是通过对被请求的用户自定义类装载器调用 loadClass()来实现的。除此以外,类装载器也可以通过调用findSystemClass()来请求启动类装载器来装载类,这是类 ClassLoader中的一个静态方法。
在版本1.2中,类装载器请求另一个类装载器来装载类型的过程被形式化,称为双亲委派模式 。
从版本1.2开始、除启动类装载器以外的每一个类装载器,都有一个“双亲”类装载器 ,在某个特定的类装载器试图以常用方式装载类型以前,它会先默认地将这个任务“委派”给它的双亲——清求它的双亲来装载这个类型。这个双亲再依次请求它自 己的双亲来装载这个类型。这个委派的过程一直向上继续,直到达到启动类装载器,通常启动类装载器是委派链中的最后一个类装载器。如果一个类装载器的双亲类 装载器有能力来装载这个类型。则这个类装载器返回这个类型。否则,这个类装载器试图自己来装载这个类。
当Java虚拟机开始运行时,在应用程序开始启动以前,它至少创建一个用户自定义装载器,也可能创建多个.所有这些装载器被连接在一个Parent-Child的委托链中,在这个链的顶端是启动类装载器。
例如:假设你写了一个应用程序,在虚拟机上运行它.虚拟机在启动时实例化了两个用户自定义类装载器:一个"扩展类装载器",一个"类路径类装载器".这些类装载器和启动类装载器一起联入一个Parent-Child委托链中,如下图所示.
上图所示类路径类装载器的Parent是扩展类装载器, 扩展类装载器的Parent是启动类装载器.在图2中,类路径类装载器就被实例为系统类装载器.假设你的程序实例化它的网络类装载器,它就指明了系统类装载器作为它的Parent.
下面的例程说明了类装载器的父子关系.
package test; import java.net.URL; import java.net.URLClassLoader; public class ClassLoaderTest { private static int count = -1; public static void testClassLoader(Object obj) { if (count < 0 && obj == null) { System.out.println("Input object is NULL"; return; } ClassLoader cl = null; if (obj != null && !(obj instanceof ClassLoader)) { cl = obj.getClass().getClassLoader(); } else if (obj != null) { cl = (ClassLoader) obj; } count++; String parent = ""; for (int i = 0; i < count; i++) { parent += "Parent "; } if (cl != null) { System.out.println( parent + "ClassLoader name = " + cl.getClass().getName()); testClassLoader(cl.getParent()); } else { System.out.println( parent + "ClassLoader name = BootstrapClassLoader"; count = -1; } } public static void main(String[] args) { URL[] urls = new URL[1]; URLClassLoader urlLoader = new URLClassLoader(urls); ClassLoaderTest.testClassLoader(urlLoader); } }
以上例程的输出为:
ClassLoader name = java.net.URLClassLoader
Parent ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent Parent ClassLoader name = BootstrapClassLoader
类装载器请求过程
以上例程1为例.将main方法改为:
ClassLoaderTest tc = new ClassLoaderTest();
ClassLoaderTest.testClassLoader(tc);
输出为:
ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent ClassLoader name = BootstrapClassLoader
程序运行过程中,类路径类装载器发出一个装载ClassLoaderTest类的请求, 类路径类装载器必须首先询问它的Parent---扩展类装载器 ---来查找并装载这个类,同样扩展类装载器首先询问启动类装载器。由于ClassLoaderTest不是 Java API(JAVA_HOME\jre\lib)中的类,也不在已安装扩展路径(JAVA_HOME\jre\lib\ext)上,这两类装载器 都将返回而不会提供一个名为ClassLoaderTest的已装载类给类路径类装载器。类路径类装载器只能以它自己的方式来装载 ClassLoaderTest,它会从当前类路径上下载这个类。这样,ClassLoaderTest就可以在应用程序后面的执行中发挥作用。
在上例中,ClassLoaderTest类的testClassLoader方法被首次调用,该方法引用了Java API中的类 java.lang.String。Java虚拟机会请求装载ClassLoaderTest类的类路径类装载器来装载 java.lang.String。就像前面一样,类路径类装载器首先将请求传递给它的Parent类装载器,然后这个请求一路被委托到启动类装载器。但 是,启动类装载器可以将java.lang.String类返回给类路径类装载器,因为它可以找到这个类,这样扩展类装载器就不必在已安装扩展路径中查找 这个类,类路径类装载器也不必在类路径中查找这个类。扩展类装载器和类路径类装载器仅需要返回由启动类装载器返回的类java.lang.String。 从这一刻开始,不管何时ClassLoaderTest类引用了名为java.lang.String的类,虚拟机就可以直接使用这个 java.lang.String类了。
4、一个经典的实例说明
我们看看下面的代码:
package java.lang; public class String { public static void main(String[] args){ } }
大家发现什么不同了吗?对了,我们写了一个与JDK中String一模一样的类,连包java.lang都一样,唯一不同的是我们自定义的String类有一个main函数。我们来运行一下:
java.lang.NoSuchMethodError: main
Exception in thread "main"
这是为什么? 我们的String类不是明明有main方法吗?
其实联系我们上面讲到的双亲委托模型,我们就能解释这个问题了。
运行这段代码,JVM会首先创建一个自定义类加载器,不妨叫做AppClassLoader,并把这个加载器链接到委托链中:AppClassLoader -> ExtClassLoader -> BootstrapLoader。
然后AppClassLoader会将加载java.lang.String的请求委托给ExtClassLoader,而 ExtClassLoader又会委托给最后的启动类加载器BootstrapLoader。
启动类加载器BootstrapLoader只能加载JAVA_HOME\jre\lib中的class类(即J2SE API),问题是标准API中确实有一个java.lang.String(注意,这个类和我们自定义的类是完全两个类)。BootstrapLoader以为找到了这个类,毫不犹豫的加载了j2se api中的java.lang.String。
最后出现上面的加载错误(注意不是异常,是错误,JVM退出),因为API中的String类是没有main方法的。
结论:我们当然可以自定义一个和API完全一样的类,但是由于双亲委托模型,使得我们不可能加载上我们自定义的这样一个类。所以J2SE规范中希望我们自定义的包有自己唯一的特色(网络域名)。还有一点,这种加载器原理使得JVM更加安全的运行程序,因为黑客很难随意的替代掉API中的代码了。
JVM加载class文件的原理的更多相关文章
- Java提高篇——JVM加载class文件的原理机制
在面试java工程师的时候,这道题经常被问到,故需特别注意. 1.JVM 简介 JVM 是我们Javaer 的最基本功底了,刚开始学Java 的时候,一般都是从“Hello World ”开始的,然后 ...
- JVM加载class文件的原理机制(转)
JVM加载class文件的原理机制 1.Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中 2.java中的 ...
- JVM加载class文件的原理机制
Java中的所有类,都需要由类加载器装载到JVM中才能运行.类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中.在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的 ...
- 【Java面试题】46 描述一下JVM加载class文件的原理机制?
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类. 由于Java的跨平台性,经过 ...
- 描述一下JVM加载class文件的原理机制
Java中的所有类,都需要由类加载器装载到JVM中才能运行.类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中.在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的 ...
- 描述一下JVM加载class文件的原理机制?
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类. 由于Java的跨平台性,经过编 ...
- 描述一下 JVM 加载 class 文件的原理机制?
JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的 类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件 中的类. 由于 Java 的 ...
- JVM 加载class文件的原理
PS:类的装载过程是ClassLoader,只有把类 加载 到 JVM后才能运行: PS:两种加载类的方式: 显示:new 隐式:class.forname() PS:动态的执行,用到谁,加载谁:
- Java面试题系列(一)描述一下JVM加载class文件的原理机制
JVM系列第4讲:从源代码到机器码,发生了什么? https://www.cnblogs.com/chanshuyi/p/jvm_serial_04_from_source_code_to_machi ...
随机推荐
- Careercup - Microsoft面试题 - 5120588943196160
2014-05-10 22:58 题目链接 原题: Three points are given A(x1, y1), B(x2, y2), C(x3, y3). Write a method ret ...
- Careercup - Microsoft面试题 - 5752271719628800
2014-05-10 20:31 题目链接 原题: Given an array of integers and a length L, find a sub-array of length L su ...
- C#制作高仿360安全卫士窗体(四)- 水晶按钮
项目越来越紧,我也乐此不疲.自从上次C#制作高仿360安全卫士窗体(三)出来之后,就开始有一些人在说为什么还在坚持写这么落后的东西.我想说的是,我是从事企业信息化工作的,所有程序都只对内部使用.所以只 ...
- android开发 两张bitmap图片合成一张图片
场景:对android4.4解码gif(解码文章见前面一篇)后的图片进行每帧处理,android4.3 解码出来的每帧都很完整,但是到android4.4版本就不完整了,每帧都是在第一帧的基础上把被改 ...
- 【Binary Tree Maximum Path Sum】cpp
题目: Given a binary tree, find the maximum path sum. The path may start and end at any node in the tr ...
- JS判断用户是否在线的方法
在以前坐项目的时候,经常碰见通过sessionLisnter来判断用户是否在线的方法,但是由于用户关闭浏览器时不会立刻是否session,因此大部分时候都考虑在页面中通过JS来监控页面是否关闭. 网络 ...
- 【BZOJ】【1211】【HNOI2004】树的计数
Prufer序列+组合数学 嗯哼~给定每个点的度数!求树的种数!那么很自然的就想到是用prufer序列啦~(不知道prufer序列的……自己再找找资料吧,这里就不放了,可以去做一下BZOJ1005明明 ...
- Metasploit Framework命令汇总
一.msfconsole ? 帮助菜单back 从当前环境返回banner 显示一个MSF bannercd 切换目录color 颜色转换connect 连接一个主机exit 退出MSFhelp 帮助 ...
- PHP第一课:开发环境配置
最近在学php,大概了解了一下php的语法结构,以及一些php及基础的知识.由此想到了要亲手试一试:以为以前是学java的用的 ide是myeclipse,所以对eclipse软件布局有特别的钟爱. ...
- python 小试牛刀之信息管理
这个是之前写的半成品,但是一直没有好好的写完,今晚我把它补充完整,并且贴出了遇到的问题 这个程序并没有处理中文,主要是python 2.7对于中文的支持太蛋疼了,虽然可以设置utf8编码,但是如果列表 ...