`
gongmingwind
  • 浏览: 588263 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

JAVA 类加载原理收藏

阅读更多
该文章来源于网上,如果侵权请留言。

这篇文章将比较深入的介绍java类加载原理和过程,虽然很多情况你都不需要了解类加载的过程,但是如果你希望了解java是如何工作的,或者希望定义自己的类加载器,就一定要了解类加载的过程,当然,无论你是否要参与类加载过程,了解这个过程对你都会有很大的帮助。本文希望通过一步一步的分析来说明类加载的过程,希望这种分析方法也会对你有某些帮助。本文使用的JDK是sun的jdk_1.4.2版本,当然这里重在阐述原理,和JDK本身没有多大的关系。这篇文章试图解决下面一些问题:类加载原理引导类加载器,扩展类加载器和系统类加载器如果知道某个类是哪个类加载器加载的如果得到系统类加载器加载了那些类首先我们要分析类加载原理,java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器)

引导类加载器负责加载jdk中的系统类,这种类加载器都是用c语言实现的,在java程序中没有办法获得这个类加载器,对于java程序是一个概念而已,基本上不用考虑它的存在,像String,Integer这样的类都是由引导类加载器加载器的.

扩展类加载器负责加载标准扩展类,一般使用java实现,这是一个真正的java类加载器,负责加载jre/lib/ext中的类,和普通的类加载器一样,其实这个类加载器对我们来说也不是很重要,我们可以通过java程序获得这个类加载器。

系统类加载器,加载第一个应用类的加载器(其实这个定义并不准确,下面你将会看到),也就是执行java MainClass 时加载MainClass的加载器,这个加载器使用java实现,使用的很广泛,负责加载classpath中指定的类。

类加载器之间有一定的关系(父子关系),我们可以认为扩展类加载器的父加载器是引导类加载器(当然不这样认为也是可以的,因为引导类加载器表现在java中就是一个null),不过系统类加载器的父加载器一定是扩展类加载器,类加载器在加载类的时候会先给父加载器一个机会,只有父加载器无法加载时才会自己去加载。我们无法获得引导类加载器,因为它是使用c实现的,而且使用引导类加载器加载的类通过getClassLoader方法返回的是null.所以无法直接操作引导类加载器,但是我们可以根据Class.getClassLoader方法是否为null判断这个类是不是引导类加载器加载的,可以通过下面的方法获得引导类加载器加载的类路径(每个jar包或者文件夹对应了一个URL);

sun.misc.Launcher.getBootstrapClassPath().getURLs()你可以直接在你的main函数中输出就可以了System.out.println(java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString());

得到的结果是:[file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/rt.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/i18n.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/sunrsasign.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jsse.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jce.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/charsets.jar, file:/C:/Program%20Files/Java/j2re1.4.2_10/classes]

其实我们是可以指定引导类加载器的类路径的,java提供了一个-Xbootclasspath参数,不过这个参数不是标准参数。

java -Xbootclasspath: 运行时指定引导类加载器的加载路径(jar文件或者目录)java -Xbootclasspath/p:和上面的相同,不过把这个路径放到原来的路径前面java -Xbootclasspath/a:这个就是在原引导类路径后面添加类路径。上面我们有提过加载第一个应用类未必就是系统加载器。如果我把这个应用类的路径放到引导类路径中,它将会被引导类加载器加载,大致这样java -Xbootclasspath/a:myjar.jar MainClass如果MainClass在myjar.jar中,那么这个类将会被引导类加载器加载。如果希望看详情,使用-verbose参数,为了看的更清楚,使用重定向,大致为(windows下):

java -verbose -Xbootclasspath/a:myjar.jar MainClass -> C:\out.txt

通过这个参数我们可以实现自己的系统类,比如替换掉java.lang.Object的实现,自己可以扩展一些方法,不过这样做似乎没有好处,因为那就不是标准了。

我们最关心的还是系统类加载器,一般都认为系统类加载器是加载应用程序第一个类的加载器,也就是java MainClass命令中加载MainClass的类加载器,这种说法虽然不是很严谨,但基本上还是可以这样认为的,因为我们很少会改变引导类加载器和扩展类加载器的默认行为。

应该说系统类加载器负责加载classpath路径中的而且没有被扩展类加载器加载的类(当然也包括引导类加载器加载的)。如果classpath中有这个类,但是这个类也在扩展类加载器的类路径,那么系统类加载器将没有机会加载它。我们很少改变扩展类加载器的行为,所以一般你自己定义的类都是系统类加载器加载器的。获得系统类加载器非常简单,假设MyClass是你定义的一个类MyClass.class.getClassLoader()返回的就是系统类加载器,当然这种方法无法保证绝对正确,我们可以使用更简单而且一定正确的方式:ClassLoader.getSystemClassLoader()获得系统类加载器。

我们知道ClassLoader是一个抽象类,所以系统类加载器肯定是ClassLoader的一个子类实现。我们来看看它是什么ClassLoader.getSystemClassLoader().getClass();

结果是class sun.misc.Lancher$AppClassLoader 可以看出这是sun的一个实现,从名字可以看出是一个内部类,目前我也没有看到这个源代码,似乎还不是很清晰:我们在看看它的父类是什么:

ClassLoader.getSystemClassLoader().getClass().getSuperclass();

结果是:class java.net.URLClassLoader这个是j2se的标准类,它的父类是SecureClassLoader,而SecureClassLoader是继承ClassLoader的。

现在整个关系应该很清楚,我们会看到几乎所有的ClassLoader实现都是继承URLClassLoader的。

因为系统类加载器是非常重要的,而且是我们可以直接控制的,所以我们后面还会介绍,不过先来看一下扩展类加载器以及它们之间的关系。扩展类加载器似乎是一个不起眼的角色,它负责加载java的标准扩展(jre/lib/ext目录下的所有jar),它其实就是一个普通的加载器,看得见摸得着的

。首先的问题是怎么知道扩展类加载器在哪里?的确没有直接途径获得扩展类加载器,但是我们知道它是系统类加载器的父加载器,我们已经很容易的获得系统类加载器了,所以我们可以间接的获得扩展类加载器:ClassLoader.getSystemClassLoader().getParent().getClass();其实是通过系统类加载器间接的获得了扩展类加载器,看看是什么东西:

结果是:class sun.misc.Launcher$ExtClassLoader这个类和系统类加载器一样是一个内部类,而且定义在同一个类中。同样看看它的父类是什么:ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass();

可以看出结果也是class java.net.URLClassLoader扩展类加载jre/lib/ext目录下的所有类,包括jar,目录下的所有类(目录名不一定要classes).现在可以回答上面的问题了,你写一个HelloWorld,放到jre/lib/ext/下的某个目录比如 jre/lib/ext/myclass/HelloWorld.class然后在你classpath也设置一份到这个类的路径,结果执行java HelloWorld时,这个类是被扩展类加载器加载器的,可以这样证明public static void main(String[] args){ System.out.println("loaded by"+HelloWorld.class.getClassLoader().getClass()); System.out.println("Hello World");}

结果可以得到class sun.misc.Launcher$ExtClassLoader当然如果你把jre/lib/ext下myclass这个目录删除,仍然可以运行,但是这样结果是class sun.misc.Lancher$AppClassLoader如果你不知道这个过程的话,假设在你扩展类路径下有一份classpath中的拷贝,或者是比较低的版本,当你使用新的版本时会发现没有起作用,知道这个过程你就不会觉得奇怪了。

另外就是两个不同的类加载器是可以加载一个同名的类的,也就是说虽然扩展类加载器加载了某个类,系统类加载器是可以加载自己的版本的,但是现有的实现都没有这样做,ClassLoader中的方法是会请求父类加载器先加载的,如果你自己定义类加载器完全可以修改这种默认行为,甚至可以让他没有父加载器。
这里给出一个方法如何获得扩展类加载器加载的路径:   
  String path=System.getProperty("java.ext.dirs");     

   File dir=new File(path);   

     if(!dir.exists()||!dir.isDirectory()){       

     return Collections.EMPTY_LIST;        }      

File[] jars=dir.listFiles();   

     URL[] urls=new URL[jars.length];     

   for(int i=0;i<jars.length;i++){        

    urls[i]=sun.misc.URLClassPath.pathToURLs(jars[i].getAbsolutePath())[0];  

      }       

return Arrays.asList(urls);

说明:通过上面的代码可以看出扩展类加载器只能加载ext目录下的jar包.

对于扩展类加载器我们基本上不会去关心,也很少把你自己的jar放到扩展路径,大部分情况下我们都感觉不到它的存在,当然如果你一定要放到这个目录下,一定要知道这个过程,它会优先于classpath中的类。现在我们应该很清楚知道某个类是哪个加载器加载的,并且知道为什么是它加载的,如果要在运行时获得某个类的类加载器,直接使用Class的getClassLoader()方法就可以了。用户定义的类一般都是系统类加载器加载的,我们很少直接使用类加载器加载类,我们甚至很少自己加载类。因为类在使用时会被自动加载,我们用到某个类时该类会被自动加载,比如new A()会导致类A自动被加载,不过这种加载只发生一次。我们也可以使用系统类加载器手动加载类,ClassLoader提供了这个接口

ClassLoader.getSystemClassLoader().loadClass("classFullName");

这就很明确的指定了使用系统类加载器加载指定的类,但是如果该类能够被扩展类加载器加载,系统类加载器还是不会有机会的。我们最常用的还是使用Class.forName加载使用的类,这种方式没有指定某个特定的ClassLoader,会使用调用类的ClassLoader。也就是说调用这个方法的类的类加载器将会用于加载这个类。比如在类A中使用Class.forName加载类B,那么加载类A的类加载器将会用于加载类B,这样两个类的类加载器是同一个。

最后讨论一下如何获得某个类加载器加载了哪些类,这个似乎有一定的使用价值,可以看出哪些类被加载了。其实这个也不是很难,因为ClassLoader中有一个classes成员变量就是用来保存类加载器加载的类列表,而且有一个方法void addClass(Class c) { classes.addElement(c);}这个方法被JVM调用。我们只要利用反射获得classes这个值就可以了,不过classes声明为private的,我们需要修改它的访问权限(没有安全管理器时很容易做到)

classes = ClassLoader.class.getDeclaredField("classes");

classes.setAccessible(true);

List ret=(List) classes.get(cl);

//classes是一个Vector可惜的是对于引导类加载器没有办法获得加载的类,因为它是c实现的,在java中很难控制了。

总结说明:
java中共有三种类型的类加载器:
    1、引导类加载器(用来加载java API类)
     2、扩展类加载器(用来加载jre\lib\ext目录下的jar包)
     3、系统类加载器(主要用来加载用户自定义的类)


这三种类加载器之间存在父子

完整测试代码如下
package com.mz.hw;
public class HelloWorld{
	  public static void main(String[] args){
			System.out.println("Hello World!");
			System.out.println("HelloWorld的类加在器:"+HelloWorld.class.getClassLoader());
			
			//启动类加载器
			System.out.println("引导类(启动类)加载器加载的包:"+java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString());
			
			
			//系统类加载器
			System.out.println("获得系统类加载器:"+ClassLoader.getSystemClassLoader());
			System.out.println("获得系统类加载器的父类:"+ClassLoader.getSystemClassLoader().getClass().getSuperclass());
			//查看系统类加载器加载了哪些类?
			try{
				ClassLoader cl = ClassLoader.getSystemClassLoader();
				java.lang.reflect.Field classes = ClassLoader.class.getDeclaredField("classes");
				classes.setAccessible(true);//修改字段的可访问权限.
				java.util.List ret=(java.util.List)classes.get(cl);
				System.out.println("系统类加载类列表:"+ret.toString());
			}catch(Exception e){
					
			}
			
			
			//扩展类加载器
			System.out.println("获得扩展类加载器:"+ClassLoader.getSystemClassLoader().getParent().getClass());
			System.out.println("获得扩展类加载器的父类:"+ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass());
			
			
			
		}
}

     
分享到:
评论

相关推荐

    Java网编程原理与JSP.Web开发核心技术

    考虑到给定的信息,我们将不包含重复的“每日书籍更新收藏必备***”这一部分内容,我们将专注于阐述“Java网编程原理与JSP.Web开发核心技术”这一主题的知识点。 首先,“Java网编程原理”涉及的是一系列基于Java...

    java面试题库 整合完美收藏版

    这个“java面试题库 整合完美收藏版”不仅包含了以上知识点,还可能涉及最新的技术趋势,如Java 8及以后的新特性,如Lambda表达式、Stream API、Optional类等。对于希望在Java开发领域深入发展的程序员来说,这是一...

    Java经典代码(绝对值得收藏)

    经典代码可能涉及内存管理、垃圾收集、类加载机制等内容。 10. **框架应用**:Spring、MyBatis、Hibernate等Java框架简化了开发工作。经典代码可能展示如何整合和使用这些框架,以实现高效的业务逻辑。 总的来说,...

    java面试题 个人收藏

    5. **JVM**:面试中常常询问JVM内存模型(堆、栈、方法区、本地方法栈、程序计数器)、垃圾回收机制(GC算法、新生代与老年代、Full GC触发条件)、类加载机制(双亲委派模型)以及如何进行性能调优。 6. **设计...

    Java基础知识总结 - 超详细篇收藏.zip

    15. **Java虚拟机(JVM)**:了解JVM的工作原理,包括类加载机制、内存区域(堆、栈、方法区等)、垃圾回收机制(GC)和性能优化。 16. **Java内存模型与并发**:理解Java内存模型(JMM)对共享变量的处理,以及...

    JAVA开发的媒体播放器.rar

    源码质料是学习者宝贵的参考资料,通过阅读和分析这些代码,开发者可以深入理解Java在实际应用中的工作原理,特别是在多媒体处理领域的应用。 首先,JSP是Java的一种动态网页技术,它允许开发者在HTML中嵌入Java...

    大数组处理时的分时加载 --收藏

    2. **懒加载机制**:设计一个数据结构或类来保存每个数据块的信息,如位置、大小等,并实现按需加载的接口。当需要处理某个数据块时,才真正从磁盘或其他存储介质读取。 3. **缓存管理**:考虑使用缓存策略,如LRU...

    JAVA命令大全-JVM设置.rar

    2. **类加载机制**:JVM采用双亲委派模型加载类,理解这个机制有助于解决类冲突和类加载问题。 3. **JVM内存结构**:理解堆(Heap)、方法区(Method Area)、栈(Stack)、本地方法栈(Native Method Stack)和...

    2021-2022年收藏的精品资料软件工程师Java字节码开发深入解析教程解释.docx

    - 类文件结构:每个Java类文件都以魔数`OxCAFEBABE`开头,用于识别文件格式。接着是小版本号和大版本号,分别表示编译时JDK的次要和主要版本。然后是常量池,存储类中使用的字符串、类名、方法名等信息。常量池后面...

    java做的迷你小字典程序

    这为用户提供了查看和学习程序内部工作原理的机会,或者根据需要自定义和扩展程序的功能。 “数据库”是指程序可能使用某种类型的数据库存储词汇和相关的释义。可能是像SQLite这样的轻量级数据库管理系统,适合于...

    Java写的一个mp3播放器

    通过学习和研究这个项目,开发者不仅可以掌握Java图形界面设计,还能深入理解音频解码原理,并了解如何在Java中实现多媒体功能。对于有志于开发自己的音乐播放应用或者提升Java编程技能的人来说,这是一个非常有价值...

    精品资料(2021-2022年收藏)信息工程系Java专业实习报告..doc

    1. **Java基础**:实习初期,主要学习Java的基础语法,包括变量、数据类型、运算符、控制结构、类和对象等概念。此外,还涉及线程(Thread)的基本使用,理解多线程同步与并发的概念,以及集合框架(Collection),...

    java后端1年经验和技术总结

    这一顺序有助于理解类加载过程及初始化逻辑。 - **值传递与引用传递**:Java中的参数传递主要分为两种:值传递和引用传递。对于基本数据类型而言,传递的是值;而对于对象,传递的是引用。了解这一点对于正确处理...

    java web 浏览器

    根据提供的文件信息,我们...对于初学者来说,这是一个很好的实践项目,可以帮助理解Java Swing组件的使用以及网络编程的基本原理。此外,该项目也为进一步扩展功能(如支持多标签页浏览、增强安全性等)提供了基础。

    eclipse-java-galileo-SR2-win32

    了解这些内容对于开发者来说至关重要,因为它们有助于理解Eclipse的工作原理,以及如何定制和优化个人开发环境。同时,熟悉不同版本的Eclipse可以帮助开发者解决兼容性问题,尤其是在处理旧项目或需要在特定环境中...

    Java_PageRecord.rar_favoritez77

    2. **分页查询原理**:分页查询是将大数据集按一定数量(如每页10条)进行切割,每次只获取一部分数据,以减轻服务器压力,提高页面加载速度。分页主要涉及两个参数:当前页码(currentPage)和每页显示的条数...

    Java Servlet Bible

    1. 加载与初始化:当第一个请求到达Servlet时,容器会加载Servlet类并创建其实例,然后调用init()方法进行初始化。 2. 服务:每当有新的请求到达,Servlet容器会调用Servlet实例的service()方法,根据请求类型转发到...

    高级java笔试题-itresource:程序开发技术资源集合

    从Jar文件中动态加载类 ] () [ Java并发源码分析 - ThreadPoolExecutor ] () [ java ClassLoader 基本原理 ] () [ 解决eclipse显示jar源代码中文乱码问题 ] () [ 使用 RMI + ZooKeeper 实现远程调用框架 ] () [ Java...

    值得收藏的2017年Java开发岗位面试题

    Class.forName和classloader都是用于加载类的,但Class.forName只能加载已经加载的类,而classloader可以加载新的类。 7. Java7、Java8的新特性。 Java7和Java8都引入了许多新特性,例如Java7的try-with-resources...

Global site tag (gtag.js) - Google Analytics