java类加载机制简述_java类加载机制概述1

从事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/)

b1bdf9b3b7062d930125f10dbd8f3252.png

在安装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.class

Assembly 是一个接口,ClassA ClassB ClassC都实现了这个接口,Test主程序在运行时将参数名作为Class名动态加载。命令行输入java -verbose:class Test ClassA 执行结果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值