`
terry0501
  • 浏览: 313651 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

JVM加载.class文件机制

阅读更多

Java 语言是一种具有动态性的解释型编程语言,当指定程序运行的时候, Java 虚拟机就将编译生成的 . class 文件按照需求和一定的规则加载进内存,并组织成为一个完整的 Java 应用程序。 Java 语言把每个单独的类 Class 和接口 Implements 编译成单独的一个 . class 文件,这些文件对于 Java 运行环境来说就是一个个可以动态加载的单元。正是因为 Java 的这种特性,我们可以在不重新编译其它代码的情况下,只编译需要修改的单元,并把修改文件编译后的 . class 文件放到 Java 的路径当中, 等到下次该 Java 虚拟机器重新激活时,这个逻辑上的 Java 应用程序就会因为加载了新修改的 .class 文件,自己的功能也做了更新,这就是 Java 的动态性。

下面用一个简单的例子让大家对 Java 的动态加载有一个基本的认识:

Java代码 复制代码
  1. class <SPAN style="COLOR: #000000; BACKGROUND-COLOR: #ffffff">TestClassA</SPAN>{    
  2.   
  3. public void method(){    
  4.   
  5. System.out.println("Loading ClassA");    
  6.   
  7. }    
  8.   
  9. }    
  10.   
  11. public class ClassLoaderTest {    
  12.   
  13. public static void main(String args[]){    
  14.   
  15. TestClassA testClassA = new TestClassA();    
  16.   
  17. testClassA.method();    
  18.   
  19. }    
  20.   
  21. }   
Java代码 复制代码 收藏代码
  1. class <SPAN style="BACKGROUND-COLOR: #ffffff; COLOR: #000000">TestClassA</SPAN>{    
  2.   
  3. public void method(){    
  4.   
  5. System.out.println("Loading ClassA");    
  6.   
  7. }    
  8.   
  9. }    
  10.   
  11. public class ClassLoaderTest {    
  12.   
  13. public static void main(String args[]){    
  14.   
  15. TestClassA testClassA = new TestClassA();    
  16.   
  17. testClassA.method();    
  18.   
  19. }    
  20.   
  21. }   
class TestClassA{ 

public void method(){ 

System.out.println("Loading ClassA"); 

} 

} 

public class ClassLoaderTest { 

public static void main(String args[]){ 

TestClassA testClassA = new TestClassA(); 

testClassA.method(); 

} 

} 

 
编译后输入命令: java -verbose:class ClassLoaderTest ,执行文件。


从运行结果我们可以看到, JRE ( JavaRuntime Environment )首先加载 ClassLoaderTest 文件,然后再加载 TestClassA 文件,从而实现了动态加载。

1. 预先加载与依需求加载

Java 运行环境为了优化系统,提高程序的执行速度,在 JRE 运行的开始会将 Java 运行所需要的基本类采用预先加载( pre-loading )的方法全部加载要内存当中,因为这些单元在 Java 程序运行的过程当中经常要使用的,主要包括 JRE 的 rt.jar 文件里面所有的 .class 文件。

当 java.exe 虚拟机开始运行以后,它会找到安装在机器上的 JRE 环境,然后把控制权交给 JRE , JRE 的类加载器会将 lib 目录下的 rt.jar 基础类别文件库加载进内存,这些文件是 Java 程序执行所必须的,所以系统在开始就将这些文件加载,避免以后的多次 IO 操作,从而提高程序执行效率。

我们可以看到多个基础类被加载, java.lang.Object,java.io.Serializable 等等。



相对于预先加载,我们在程序中需要使用自己定义的类的时候就要使用依需求加载方法( load-on-demand ),就是在 Java 程序需要用到的时候再加载,以减少内存的消耗,因为 Java 语言的设计初衷就是面向嵌入式领域的。

在这里还有一点需要说明的是, JRE 的依需求加载究竟是在什么时候把类加载进入内部的呢?

我们在定义一个类实例的时候,比如 TestClassA testClassA ,这个时候 testClassA 的值为 null ,也就是说还没有初始化,没有调用 TestClassA 的构造函数,只有当执行 testClassA = new TestClassA() 以后, JRE 才正真把 TestClassA 加载进来。

2. 隐式加载和显示加载

Java 的加载方式分为隐式加载( implicit )和显示加载( explicit ),上面的例子中就是用的隐式加载的方式。所谓隐式加载就是我们在程序中用 new 关键字来定义一个实例变量, JRE 在执行到 new 关键字的时候就会把对应的实例类加载进入内存。隐式加载的方法很常见,用的也很多, JRE 系统在后台自动的帮助用户加载,减少了用户的工作量,也增加了系统的安全性和程序的可读性。

相对于隐式加载的就是我们不经常用到的显示加载。所谓显示加载就是有程序员自己写程序把需要的类加载到内存当中,下面我们看一段程序:

Java代码 复制代码
  1. class TestClass{    
  2.   
  3. public void method(){    
  4.   
  5. System.out.println("TestClass-method");    
  6.   
  7. }    
  8.   
  9. }    
  10.   
  11. public class CLTest {    
  12.   
  13. public static void main(String args[]) {    
  14.   
  15. try{    
  16.   
  17. Class c = Class.forName("TestClass");    
  18.   
  19. TestClass object = (TestClass)c.newInstance();    
  20.   
  21. object.method();    
  22.   
  23. }catch(Exception e){    
  24.   
  25. e.printStackTrace();    
  26.   
  27. }    
  28.   
  29. }    
  30.   
  31. }   
Java代码 复制代码 收藏代码
  1. class TestClass{    
  2.   
  3. public void method(){    
  4.   
  5. System.out.println("TestClass-method");    
  6.   
  7. }    
  8.   
  9. }    
  10.   
  11. public class CLTest {    
  12.   
  13. public static void main(String args[]) {    
  14.   
  15. try{    
  16.   
  17. Class c = Class.forName("TestClass");    
  18.   
  19. TestClass object = (TestClass)c.newInstance();    
  20.   
  21. object.method();    
  22.   
  23. }catch(Exception e){    
  24.   
  25. e.printStackTrace();    
  26.   
  27. }    
  28.   
  29. }    
  30.   
  31. }   
class TestClass{ 

public void method(){ 

System.out.println("TestClass-method"); 

} 

} 

public class CLTest { 

public static void main(String args[]) { 

try{ 

Class c = Class.forName("TestClass"); 

TestClass object = (TestClass)c.newInstance(); 

object.method(); 

}catch(Exception e){ 

e.printStackTrace(); 

} 

} 

} 

 
我们通过 Class 类的 forName (String s) 方法把自定义类 TestClass 加载进来,并通过 newInstance ()方法把实例初始化。事实上 Class 类还很多的功能,这里就不细讲了,有兴趣的可以参考 JDK 文档。

Class 的 forName() 方法还有另外一种形式: Class forName(String s, boolean flag, ClassLoader classloader) , s 表示需要加载类的名称, flag 表示在调用该函数加载类的时候是否初始化静态区, classloader 表示加载该类所需的加载器。

forName (String s) 是默认通过 ClassLoader.getCallerClassLoader() 调用类加载器的,但是该方法是私有方法,我们无法调用,如果我们想使用 Class forName(String s, boolean flag, ClassLoader classloader) 来加载类的话,就必须要指定类加载器,可以通过如下的方式来实现:

Java代码 复制代码
  1. Test test = new Test();//Test 类为自定义的一个测试类;    
  2.   
  3. ClassLoader cl = test. getClass().getClassLoader();    
  4.   
  5. // 获取 test 的类装载器;    
  6.   
  7. Class c = Class.forName("TestClass"true, cl);   
Java代码 复制代码 收藏代码
  1. Test test = new Test();//Test 类为自定义的一个测试类;    
  2.   
  3. ClassLoader cl = test. getClass().getClassLoader();    
  4.   
  5. // 获取 test 的类装载器;    
  6.   
  7. Class c = Class.forName("TestClass"true, cl);   
Test test = new Test();//Test 类为自定义的一个测试类; 

ClassLoader cl = test. getClass().getClassLoader(); 

// 获取 test 的类装载器; 

Class c = Class.forName("TestClass", true, cl); 

 

因为一个类要加载就必需要有加载器,这里我们是通过获取加载 Test 类的加载器 cl 当作加载 TestClass 的类加载器来实现加载的。

3. 自定义类加载机制

之前我们都是调用系统的类加载器来实现加载的,其实我们是可以自己定义类加载器的。利用 Java 提供的 java.net.URLClassLoader 类就可以实现。下面我们看一段范例:

Java代码 复制代码
  1. try{    
  2.   
  3. URL url = new URL("file:/d:/test/lib/");    
  4.   
  5. URLClassLoader urlCL = new URLClassLoader(new URL[]{url});    
  6.   
  7. Class c = urlCL.loadClass("TestClassA");    
  8.   
  9. TestClassA object = (TestClassA)c.newInstance();    
  10.   
  11. object.method();    
  12.   
  13. }catch(Exception e){    
  14.   
  15. e.printStackTrace();    
  16.   
  17. }   
Java代码 复制代码 收藏代码
  1. try{    
  2.   
  3. URL url = new URL("file:/d:/test/lib/");    
  4.   
  5. URLClassLoader urlCL = new URLClassLoader(new URL[]{url});    
  6.   
  7. Class c = urlCL.loadClass("TestClassA");    
  8.   
  9. TestClassA object = (TestClassA)c.newInstance();    
  10.   
  11. object.method();    
  12.   
  13. }catch(Exception e){    
  14.   
  15. e.printStackTrace();    
  16.   
  17. }   
try{ 

URL url = new URL("file:/d:/test/lib/"); 

URLClassLoader urlCL = new URLClassLoader(new URL[]{url}); 

Class c = urlCL.loadClass("TestClassA"); 

TestClassA object = (TestClassA)c.newInstance(); 

object.method(); 

}catch(Exception e){ 

e.printStackTrace(); 

} 

 
我们通过自定义的类加载器实现了 TestClassA 类的加载并调用 method ()方法。分析一下这个程序:首先定义 URL 指定类加载器从何处加载类, URL 可以指向网际网络上的任何位置,也可以指向我们计算机里的文件系统 ( 包含 JAR 文件 ) 。上述范例当中我们从 file:/d:/test/lib/ 处寻找类;然后定义 URLClassLoader 来加载所需的类,最后即可使用该实例了。

4. 类加载器的阶层体系

讨论了这么多以后,接下来我们仔细研究一下 Java 的类加载器的工作原理:

当执行 java ***.class 的时候, java.exe 会帮助我们找到 JRE ,接着找到位于 JRE 内部的 jvm.dll ,这才是真正的 Java 虚拟机器 , 最后加载动态库,激活 Java 虚拟机器。虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类加载器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰写而成,这个 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader ,并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader ,并设定其 Parent 为之前产生的 ExtClassLoader 实体。这两个加载器都是以静态类的形式存在的。这里要请大家注意的是, Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。

下面的图形可以表示三者之间的关系:

父类

父类

载入

载入

BootstrapLoader

PARENT

AppClassLoader

PARENT

ExtClassLoader

这三个加载器就构成我们的 Java 类加载体系。他们分别从以下的路径寻找程序所需要的类:

BootstrapLoader : sun.boot.class.path

ExtClassLoader: java.ext.dirs

AppClassLoader: java.class.path

这三个系统参量可以通过 System.getProperty() 函数得到具体对应的路径。大家可以自己编程实现查看具体的路径。

5. 总结

了解 Java 的类加载机制对我们熟练灵活运用 Java 语言,提高程序的运行效率有着非常重要的作用,知其然也要知其所以然,这样才能从整体提高程序的质量。

Java代码 复制代码
  1. public class ClassLoaderTest1{    
  2. private ClassLoaderTest2 test = null;    
  3. public ClassLoaderTest1(){    
  4. test = new ClassLoaderTest2();    
  5. }    
  6. public void method(){    
  7. System.out.println("Loading ClassA");    
  8. }    
  9. }    
  10.   
  11.   
  12. class ClassLoaderTest2{    
  13. public ClassLoaderTest2(){    
  14.   
  15. }    
  16. public void method(){    
  17. System.out.println("Loading ClassA");    
  18. }    
  19. }   
Java代码 复制代码 收藏代码
  1. public class ClassLoaderTest1{    
  2. private ClassLoaderTest2 test = null;    
  3. public ClassLoaderTest1(){    
  4. test = new ClassLoaderTest2();    
  5. }    
  6. public void method(){    
  7. System.out.println("Loading ClassA");    
  8. }    
  9. }    
  10.   
  11.   
  12. class ClassLoaderTest2{    
  13. public ClassLoaderTest2(){    
  14.   
  15. }    
  16. public void method(){    
  17. System.out.println("Loading ClassA");    
  18. }    
  19. }   
public class ClassLoaderTest1{ 
private ClassLoaderTest2 test = null; 
public ClassLoaderTest1(){ 
test = new ClassLoaderTest2(); 
} 
public void method(){ 
System.out.println("Loading ClassA"); 
} 
} 


class ClassLoaderTest2{ 
public ClassLoaderTest2(){ 

} 
public void method(){ 
System.out.println("Loading ClassA"); 
} 
} 

 
测试程序:

Java代码 复制代码
  1. URL url = null;    
  2. try {    
  3. url = new URL("file:/E:/JAVA/MyProject/string/");    
  4. catch (MalformedURLException e) {    
  5. e.printStackTrace();    
  6. }    
  7. URLClassLoader cl = new URLClassLoader(new URL[]{url});    
  8. URLClassLoader cl1 = new URLClassLoader(new URL[]{url});    
  9. try {    
  10. Class tempClass = cl.loadClass("ClassLoaderTest1");    
  11. Class tempClass2 = cl.loadClass("ClassLoaderTest2");    
  12. Object test = tempClass.newInstance();    
  13. System.out.println(tempClass.getClassLoader());    
  14. System.out.println(tempClass2.getClassLoader());    
  15. catch (Exception e) {    
  16. e.printStackTrace();    
  17. }   
Java代码 复制代码 收藏代码
  1. URL url = null;    
  2. try {    
  3. url = new URL("file:/E:/JAVA/MyProject/string/");    
  4. catch (MalformedURLException e) {    
  5. e.printStackTrace();    
  6. }    
  7. URLClassLoader cl = new URLClassLoader(new URL[]{url});    
  8. URLClassLoader cl1 = new URLClassLoader(new URL[]{url});    
  9. try {    
  10. Class tempClass = cl.loadClass("ClassLoaderTest1");    
  11. Class tempClass2 = cl.loadClass("ClassLoaderTest2");    
  12. Object test = tempClass.newInstance();    
  13. System.out.println(tempClass.getClassLoader());    
  14. System.out.println(tempClass2.getClassLoader());    
  15. catch (Exception e) {    
  16. e.printStackTrace();    
  17. }   
URL url = null; 
try { 
url = new URL("file:/E:/JAVA/MyProject/string/"); 
} catch (MalformedURLException e) { 
e.printStackTrace(); 
} 
URLClassLoader cl = new URLClassLoader(new URL[]{url}); 
URLClassLoader cl1 = new URLClassLoader(new URL[]{url}); 
try { 
Class tempClass = cl.loadClass("ClassLoaderTest1"); 
Class tempClass2 = cl.loadClass("ClassLoaderTest2"); 
Object test = tempClass.newInstance(); 
System.out.println(tempClass.getClassLoader()); 
System.out.println(tempClass2.getClassLoader()); 
} catch (Exception e) { 
e.printStackTrace(); 
} 

 
当ClassLoaderTest1,ClassLoaderTest2在当前目录和E:/JAVA/MyProject/string/都存在的时候输出为sun.misc.Launcher$AppClassLoader@1050169
sun.misc.Launcher$AppClassLoader@1050169
即都是被AppClassLoader加载的, 即使在E:/JAVA/MyProject/string/下面也存在.

当ClassLoaderTest1,ClassLoaderTest2只在E:/JAVA/MyProject/string/下存在的时候输出为
java.net.URLClassLoader@480457
java.net.URLClassLoader@1a7bf11
即都是被自定义的加载器加载的,并且也可以Object test = tempClass.newInstance();

下面一的是最关键的,因为ClassLoaderTest1需要用到ClassLoaderTest2,如果ClassLoaderTest2被AppClassLoader加载,而ClassLoaderTest1是被自定义的类加载器加载,就会出现如下错误:

java.lang.IllegalAccessError: tried to access class ClassLoaderTest2 from class ClassLoaderTest1
at ClassLoaderTest1. <init>(ClassLoaderTest1.java:6)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
at java.lang.Class.newInstance0(Class.java:308)
at java.lang.Class.newInstance(Class.java:261)
at ClassLoaderTest.main(ClassLoaderTest.java:43)

分享到:
评论

相关推荐

    jvm 加载class文件

    ### JVM加载Class文件详解 #### 一、Java与JVM中的Class文件加载机制概述 Java作为一种动态性极强的解释型编程语言,在程序运行时,Java虚拟机(JVM)负责将编译生成的`.class`文件加载到内存中进行执行。在Java...

    JVM加载class文件的原理机制

    JVM加载class文件的原理机制 JVM加载class文件的原理机制是Java中的核心机制之一,由于Java中的所有类必须被装载到JVM中才能运行,这个装载工作是由JVM中的类装载器完成的。类装载器所做的工作实质是把类文件从硬盘...

    JVM加载class文件的原理机制.pdf

    JVM加载class文件的原理机制 JVM加载class文件的原理机制是Java虚拟机中一个非常重要的组件,负责将class文件加载到内存中,以便Java程序的执行。下面是JVM加载class文件的原理机制的详细介绍: 类加载的原理 在...

    JVM原理.pdf

    冯立全在分享中重点介绍了JVM运行机制,尤其强调了运行时数据区域的布局、Class文件的结构、字节码技术、类加载机制以及垃圾回收算法等核心内容。 运行时数据区域是JVM内存模型的核心,它被划分为若干部分,主要...

    codeegginterviewgroup#CodeEggDailyInterview#84.JVM加载class文件的原理机制

    JVM加载class文件的原理机制JVM加载class文件的原理机制 JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加

    class文件编译器.zip

    在Java编程语言中,`class`文件是程序的二...综上所述,`class文件编译器.zip`可能包含了与Java编译相关的工具、教程或示例,涵盖了从源代码到可执行字节码的整个过程,对于学习和理解Java编译机制是非常有价值的资源。

    JVM基础.doc

    **ClassLoader** 负责加载ClassFile到JVM中,它遵循双亲委派模型。常见的ClassLoader包括Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。 #### 四、内存模型、锁与同步 **Java内存模型** 主要...

    jvm解析编译过的class文件

    1. **加载**:JVM通过类加载器(ClassLoader)找到并读取.class文件。类加载器按照双亲委托模型工作,首先询问父加载器,如果父加载器无法加载,则由当前加载器尝试。加载过程中,JVM会创建一个对应的Class对象。 2...

    JVM类加载过程.pptx

    JVM的类加载机制是JVM的核心机制之一,它把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初识化,形成可以被虚拟机直接使用的Java类型。 Java代码执行流程是JVM的核心流程之一,它首先通过...

    JVM调优篇.pdf

    JVM类加载机制详解 JVM类加载机制是Java虚拟机中的一种机制,它负责加载Java类文件到内存中,以便执行Java程序。类加载机制分为五个阶段:加载、验证、准备、解析和初始化。 加载 加载是类加载过程中的一个阶段,...

    agent7:Java代理以重新加载.class文件; 它使用Java 7+中可用的文件监视API

    Agent7旨在变得简单,供独立的JVM程序使用以在开发模式下重新加载.class文件。 更改后,它仅在类路径中重新加载.class文件。 “ Agent7”中的“ 7”是因为Agent7使用Java 7+中可用的文件监视API。 您需要使用Java 7...

    全面理解JVM虚拟机.rar

    了解CLASS文件格式能帮助开发者更好地理解代码在JVM中的表现。 3. 类加载 类加载机制包括加载、验证、准备、解析和初始化五个阶段。JVM使用类加载器(ClassLoader)来查找和加载类,双亲委派模型确保了类的唯一性...

    JVM笔记.docx

    1. 类装载器(ClassLoader):负责加载.class文件,将字节码内容解析并存储到内存中,同时根据双亲委派模型进行类的加载。类装载器分为启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader...

    反编译java.class文件

    - **加载.class文件**:在JD-GUI界面中,点击“Open”按钮,选择需要反编译的.java.class文件。 - **查看源代码**:JD-GUI会自动反编译选定的.class文件,并显示在源代码视图中。你可以浏览方法、类结构、变量等...

    JVM面试.docx

    - 类加载器将`.class`文件中的字节码加载到方法区中,并创建对应的`Class`对象。 2. **执行`main`方法**: - 一旦主类被加载,JVM就会找到`public static void main(String[] args)`方法并开始执行。 3. **对象...

    jvm虚拟机.xmind

    了解Jvm的发展历程即结构,讲解jvm的结构、内存模型、类加载机制,gc的算法分类、class文件的类型

    jvmjava,java实现的JVM。.zip

    JVM通过解析.class文件中的字节码,将其转换为机器码,从而在本地操作系统上执行。理解JVM的工作机制对于优化Java应用程序性能至关重要。 二、开源项目的意义 开源项目“jvmjava”为开发者提供了一种学习JVM内部...

    Java分布式应用学习笔记-谈JVM.doc

    JVM的主要任务包括:将.java文件编译成.class文件、加载已有.class文件、执行.class文件以及管理内存分配和回收。JVM还通过与操作系统交互来实现多线程和并发。内存主要分为四个区域:方法区、堆区、栈区和本地方法...

    运行eclipse提示JVM错误的解决

    #### 二、JVM内存管理机制 JVM通过参数`-Xms`和`-Xmx`来控制分配给应用程序的内存。这些参数用于设定堆内存的初始值和最大值: - **-Xms**:设置JVM初始分配的内存,默认为物理内存的1/64。 - **-Xmx**:设置JVM...

Global site tag (gtag.js) - Google Analytics