从事java研发必然少不了对java类加载机制的涉及,本文结合例子讲述java classloader工作机制。
一 jvm 类加载机制
1)jvm位置:java是运行在java虚拟机上的程式,java虚拟机物理层面上来讲,就是我们安装在电脑上的jre目录/lib/jvm.dll(版本不同,可能存在于jre目录/lib/client/jvm.dll,jre目录/lib/server/jvm.dll),这是java字节码运行的基础,它不是由java语言编写,所以我们阅读jdk源码时遇到native函数,基本上就是调用jvm相关的代码。
2)jdk和jre关系:从oracle官网上下载java环境,可以选择jdk或者jre进行安装,他们的关系可以理解为子集的概念,jdk是jre运行环境再加上一些java开发的工具集,查看jdk目录结构如下(例子为jdk1.6.37版本)
D:. ├─bin │ └─server ├─include │ └─win32 ├─jre │ ├─bin │ │ ├─dtplugin │ │ ├─plugin2 │ │ └─server │ └─lib │ ├─amd64 │ ├─applet │ ├─audio │ ├─cmm │ ├─deploy │ ├─ext │ ├─fonts │ ├─im │ ├─images │ │ └─cursors │ ├─management │ ├─security │ ├─servicetag │ └─zi │ ├─Africa │ ├─America │ │ ├─Argentina │ │ ├─Indiana │ │ ├─Kentucky │ │ └─North_Dakota │ ├─Antarctica │ ├─Asia │ ├─Atlantic │ ├─Australia │ ├─Etc │ ├─Europe │ ├─Indian │ ├─Pacific │ └─SystemV └─lib └─visualvm ├─etc ├─platform │ ├─config │ │ ├─ModuleAutoDeps │ │ └─Modules │ ├─core │ │ └─locale │ ├─docs │ ├─lib │ │ └─locale │ ├─modules │ │ ├─ext │ │ │ └─locale │ │ └─locale │ └─update_tracking ├─profiler │ ├─config │ │ └─Modules │ ├─lib │ │ ├─deployed │ │ │ ├─jdk15 │ │ │ │ └─windows-amd64 │ │ │ └─jdk16 │ │ │ └─windows-amd64 │ │ └─locale │ ├─modules │ │ └─locale │ └─update_tracking └─visualvm ├─config │ └─Modules ├─core │ └─locale ├─modules │ └─locale └─update_tracking
java官方文档描述jre和jdk关系如图:(链接http://docs.oracle.com/javase/7/docs/)
在安装jdk时可以选择是否同时安装jre,如果选择安装,那么系统中就存在两份jre,具体程序运行时会执行哪个jre,windows系统默认搜索规则是:
1. 当前目录下有沒有 JRE子目录
2. 父目录下 JRE 子目录
3.查 詢 Window Registry(HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\)
注意:安装环境会建议建立JAVA_HOME环境变量并将其加入path中,这样可以避免因为默认搜索规则出的结果造成混淆。如果没有加,可以搜索下自己系统中有几个java.exe,本人系统中有三个,分别在c:/windows/system32/java.ext;
d:/program files/java/jdk_1_6_37/bin/java.exe;
d:/program files/java/jre/bin/java.exe
因为没有将JAVA_HOME路径加入到path,path路径是c:/windows/system32;......所以在命令行下执行java Main系统默认执行的是c:/windows/system32/java.exe(除非命令行在其他两个java.exe所在目录),这一点可以通过分别修改三个路径下java.exe文件到新名字java1.exe来验证到底执行的是哪个目录
3)java类加载机制:jdk带有三个系统类加载器:bootstrap加载器;扩展加载器;系统加载器,他们的关系如下表
类加载器 |
被加载加载器 | parent | 父类 | 类型 | 默认加载目录/文件 | 备注 |
bootstrap加载器 |
sun.boot.class.path系统属性所指路径,指向jre下/lib,如rt.jar
|
虚拟机出于安全等因素考虑,不会加载< Java_Runtime_Home >/lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的 |
||||
扩展加载器 |
bootstrap加载器 |
bootstrap加载器(因为此加载器由非java语言编写,在jvm中标识为null,所以一个加载器的parent为null表示它是由bootstrap加载器加载) |
java.lang.ClassLoader->java.security.SecurClassLoader->java.net.URLClassLoader | sun.misc.Launcher$ExtClassLoader | java.ext.dirs属性所指路径,指向java.exe所在jre下/lib/ext子目录,可以将自己的class文件放入这个目录,交由扩展加载器加载,可以通过–Djava.ext.dirs=xxx 改变 | jvm中只存在一份,一旦建立,再通过System.setProperty()修改系统属性不会起作用 |
系统加载器 |
bootstrap加载器 |
扩展加载器 |
java.lang.ClassLoader->java.security.SecurClassLoader->java.net.URLClassLoader | sun.misc.Launcher$AppClassLoader |
默认为.目录
再取java.class.path属性所指路径,可以通过java -cp xxx 来改变 最后取环境变量CLASSPATH下的class文件和jar文件 |
jvm中只存在一份,一旦建立,再通过System.setProperty()修改系统属性不会起作用 |
在 Java 之中,每个类都是由某个类型加载器(ClassLoader 的实体)来载入,因此,Class 类型的实体中,都会有记录载入它的ClassLoader 的实体(注意:如果值是null,不代表它不是由类加载器载入,而是代表这个类別是由(bootstrap loader,也有人称root loader)所载入,只不过这个类型加载器不由java书写,所以逻辑上没有实体 )
二 自定义类加载器
加载类到内存中分两种方式:1)预加载 ;2)显示加载。预加载是虚拟机在启动的时候将rt.jar中的类一次加载到内存,因为这些类都是基础类,会被频繁使用到,预加载可以减少运行时IO开销,显示加载可以:1)使用new()操作符 2)java.lang.Class 裡的forName() 3)java.lang.ClassLoader 裡的loadClass()
要查看类加载详情,可以使用java -verbose:class xxx来输出。看下面一段代码:
public class Main { public static void main(String args[]) { A a1 = new A() ; a1.print() ; B b1 = new B() ; b1.print() ; } } public class A //与Main在同一个路径下 { public void print() { System.out.println("Using Class A") ; } } public class B //与Main在同一个路径下 { public void print() { System.out.println("Using Class B") ; } }到Main所在目录执行javac *.java,查看生成了三个calss文件,再执行java -verbose:class Main > load.log ,查看load.log内容如下:
[Opened D:\Program Files\Java\jdk1.6.0_37\jre\lib\rt.jar] [Loaded java.lang.Object from D:\Program Files\Java\jdk1.6.0_37\jre\lib\rt.jar] [Loaded java.io.Serializable from D:\Program Files\Java\jdk1.6.0_37\jre\lib\rt.jar] [Loaded java.lang.Comparable from D:\Program Files\Java\jdk1.6.0_37\jre\lib\rt.jar] ... ... [Loaded java.security.Principal from D:\Program Files\Java\jdk1.6.0_37\jre\lib\rt.jar] [Loaded Main from file:/D:/deep_java/] [Loaded A from file:/D:/deep_java/] Using Class A [Loaded B from file:/D:/deep_java/] Using Class B [Loaded java.lang.Shutdown from D:\Program Files\Java\jdk1.6.0_37\jre\lib\rt.jar] [Loaded java.lang.Shutdown$Lock from D:\Program Files\Java\jdk1.6.0_37\jre\lib\rt.jar]由此可见class类加载顺序。
也可以使用以下方式加载类:
import java.net.* ; public class Test { public static void main(String args[]) throws Exception { Class c = Class.forName(args[0]) ; //第一种载入class对象方法 Object o = c.newInstance() ; //Class c = Class.forName(args[0],true,off.getClass().getClassLoader()) ;//true参数表示载入同时进行初始化,这个参数在SPI接口和实现类加载中非常有用 Test off = new Test() ; System.out.println("类型准备载入") ; //ClassLoader loader = off.getClass().getClassLoader() ;//第二种载入class对象方法,使用了对象引用Class的classloader //Class c = loader.loadClass(args[0]) ; System.out.println("类型准备实例化") ; Object o = c.newInstance() ; Object o2 = c.newInstance() ; } }
了解了默认类加载机制后,可以手工打造一个加载器,ExtClassLoader和AppClassLoader都是继承URLClassLoader,自己的加载器也可以继承自这个类:
import java.net.* ; public class Test { public static void main(String args[]) throws Exception { URL u = new URL("file:/D:/deep_java/test/lib/") ; URLClassLoader ucl = new URLClassLoader(new URL[]{ u }) ; Class c = ucl.loadClass(args[0]) ; Assembly asm = (Assembly) c.newInstance() ; asm.start() ; URL u1 = new URL("file:/D:/deep_java/test/lib/") ; URLClassLoader ucl1 = new URLClassLoader(new URL[]{ u1 }) ; Class c1 = ucl1.loadClass(args[0]) ; Assembly asm1 = (Assembly) c1.newInstance() ; asm1.start() ; System.out.println(Test.class.getClassLoader()) ; System.out.println(u.getClass().getClassLoader()) ; System.out.println(ucl.getClass().getClassLoader()) ; System.out.println(c.getClassLoader()) ; System.out.println(asm.getClass().getClassLoader()) ; System.out.println(u1.getClass().getClassLoader()) ; System.out.println(ucl1.getClass().getClassLoader()) ; System.out.println(c1.getClassLoader()) ; System.out.println(asm1.getClass().getClassLoader()) ; System.out.println(Assembly.class.getClassLoader()) ; }deep_java/test/ 目录结构如下:
├─Test.class ├─Assembly.class ├─lib │ ├─ClassA.class │ ├─ClassB.class │ ├─ClassC.classAssembly 是一个接口,ClassA ClassB ClassC都实现了这个接口,Test主程序在运行时将参数名作为Class名动态加载。命令行输入java -verbose:class Test ClassA 执行结果如下:
相关推荐
该文件是JVM中关于类加载机制的知识整理的思维导图,包括类加载机制概述、类加载的生命周期、加载时机、加载过程、类加载、类的初始化和实例化等几个大方面进行了讲解,其中类加载中还对JVM三种预定义类加载器进行了...
学习概述:本模块深入讲解了Java类加载方面的知识,Java类加载器和类加载机制以及类加载原理 学习目标:掌握类加载机制和原理,能够独立开发自己的类加载器。 1.类的加载 什么是类加载? 类加载是指将类的...
一、java反射机制概述 Reflection (反射)被视为动态语言的关键,为什么这么说呢,是因为它在运行时就确定下来了。反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的...
java.security.interfaces 提供的接口用于生成 RSA Laboratory Technical Note PKCS#1 中定义的 RSA(Rivest、Shamir 和 Adleman AsymmetricCipher 算法)密钥,以及 NIST 的 FIPS-186 中定义的 DSA(数字签名算法)...
(类的加载概述和加载时机) (类加载器的概述和分类) (获取class文件对象的三种方式) (通过反射获取无参构造方法并使用) (通过反射获取带参构造方法并使用) (通过反射获取私有构造方法并使用) (通过反射获取成员变量并...
14 JAVA类加载器CLASSLOADER 15 JAVA简单工厂模式 16 JAVA中的注解 17 JAVA 图形界面 18 JAVA多线程 19 JAVA 反射机制 20 JAVA克隆CLONE(复制) 21 JAVA 网络编程 22 JAVA 其他未归类 23 JNI概述
java.security.interfaces 提供的接口用于生成 RSA Laboratory Technical Note PKCS#1 中定义的 RSA(Rivest、Shamir 和 Adleman AsymmetricCipher 算法)密钥,以及 NIST 的 FIPS-186 中定义的 DSA(数字签名算法)...
java.security.interfaces 提供的接口用于生成 RSA Laboratory Technical Note PKCS#1 中定义的 RSA(Rivest、Shamir 和 Adleman AsymmetricCipher 算法)密钥,以及 NIST 的 FIPS-186 中定义的 DSA(数字签名算法)...
java.security.interfaces 提供的接口用于生成 RSA Laboratory Technical Note PKCS#1 中定义的 RSA(Rivest、Shamir 和 Adleman AsymmetricCipher 算法)密钥,以及 NIST 的 FIPS-186 中定义的 DSA(数字签名算法)...
第1章 Java概述 1 1.1 Java语言的发展简史 2 1.2 Java的竞争对手及各自优势 4 1.2.1 C#简介和优势 4 1.2.2 Ruby简介和优势 4 1.2.3 Python的简介和优势 5 1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6...
因此,我们可以使用该工具来快速...疯狂Java讲义笔记汇总 目录 一、基础类型 二、流程控制与数组 三、面向对象 四、基础类库 五、集合 六、泛型 七、异常 八、数据库 九、注释 十、输入输出 十一、网络 十二、类加载机制
java.security.interfaces 提供的接口用于生成 RSA Laboratory Technical Note PKCS#1 中定义的 RSA(Rivest、Shamir 和 Adleman AsymmetricCipher 算法)密钥,以及 NIST 的 FIPS-186 中定义的 DSA(数字签名算法)...
java.security.interfaces 提供的接口用于生成 RSA Laboratory Technical Note PKCS#1 中定义的 RSA(Rivest、Shamir 和 Adleman AsymmetricCipher 算法)密钥,以及 NIST 的 FIPS-186 中定义的 DSA(数字签名算法)...
/ 170 第7章 虚拟机类加载机制 / 171 7.1 概述 / 171 7.2 类加载的时机 / 172 7.3 类加载的过程 / 176 7.3.1 加载 / 176 7.3.2 验证 / 178 7.3.3 准备 / 181 7.3.4 解析 / 182 7.3.5 初始化 / 186 7.4 类...
静态初始化块(经常用来初始化类,加载类信息时执行!) 67 package 68 JDK中的主要包 68 import 68 eclipse的使用 69 继承(extend, inheritance) 70 为什么需要继承?继承的作用? 70 继承介绍 70 如何实现继承? ...
第三部分分析了虚拟机的执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎。第四部分讲解了程序的编译与代码的优化,阐述了泛型、自动装箱拆箱、条件编译等语法糖的原理;讲解了虚拟机的热点探测...
javax.management.loading 提供实现高级动态加载的类。 javax.management.modelmbean 提供了 ModelMBean 类的定义。 javax.management.monitor 提供 monitor 类的定义。 javax.management.openmbean 提供开放数据...
全书从java服务器的体系结构、开发工具和管理工具、编程技术、安全机制等四个方面全面介绍java服务器的升友技术。通过阅读本书,读者不仅能够知道用java服务器体系结构开发servlet与用传统cgi编写程序的好处,而且还...