java 类隔离_Java类装载体系中的隔离性

正文

Java中类的查找与装载出现的问题总是会时不时出现在Java程序员面前,这并

不是什么丢脸的事情,相信没有一个Java程序员没遇到过ClassNotException,因此不要为被人瞅见自己也犯这样的错误而觉得不自然,但是

在如果出现了ClassNotFoundException后异常后一脸的茫然,那我想你该了解一下java的类装载的体制了,同时为了进行下面的关于类

装载器之间的隔离性的讨论,我们先简单介绍一下类装载的体系结构。

1. Java类装载体系结构

载类的过程非常简单:查找类所在位置,并将找到的Java类的字节码装入内存,生成对应的Class对象。Java的类装载器专门用来实现这样的过

程,JVM并不止有一个类装载器,事实上,如果你愿意的话,你可以让JVM拥有无数个类装载器,当然这除了测试JVM外,我想不出还有其他的用途。你应该

已经发现到了这样一个问题,类装载器自身也是一个类,它也需要被装载到内存中来,那么这些类装载器由谁来装载呢,总得有个根吧?没错,确实存在这样的根,

它就是神龙见首不见尾的Bootstrap ClassLoader.

为什么说它神龙见首不见尾呢,因为你根本无法在Java代码中抓住哪怕是它的一点点的尾巴,尽管你能时时刻刻体会到它的存在,因为java的运行环境所需

要的所有类库,都由它来装载,而它本身是C++写的程序,可以独立运行,可以说是JVM的运行起点,伟大吧。在Bootstrap完成它的任务后,会生成

一个AppClassLoader(实际上之前系统还会使用扩展类装载器ExtClassLoader,它用于装载Java运行环境扩展包中的类),这个

类装载器才是我们经常使用的,可以调用ClassLoader.getSystemClassLoader()

来获得,我们假定程序中没有使用类装载器相关操作设定或者自定义新的类装载器,那么我们编写的所有java类通通会由它来装载,值得尊敬吧。

AppClassLoader查找类的区域就是耳熟能详的Classpath,也是初学者必须跨过的门槛,有没有灵光一闪的感觉,我们按照它的类查找范围

给它取名为类路径类装载器。还是先前假定的情况,当Java中出现新的类,AppClassLoader首先在类传递给它的父类类装载器,也就是

Extion ClassLoader,询问它是否能够装载该类,如果能,那AppClassLoader就不干这活了,同样Extion

ClassLoader在装载时,也会先问问它的父类装载器。我们可以看出类装载器实际上是一个树状的结构图,每个类装载器有自己的父亲,类装载器在装载

类时,总是先让自己的父类装载器装载(多么尊敬长辈),如果父类装载器无法装载该类时,自己就会动手装载,如果它也装载不了,那么对不起,它会大喊一

声:Exception,class not

found。有必要提一句,当由直接使用类路径装载器装载类失败抛出的是NoClassDefFoundException异常。如果使用自定义的类装载

器loadClass方法或者ClassLoader的findSystemClass方法装载类,如果你不去刻意改变,那么抛出的是

ClassNotFoundException。

我们简短总结一下上面的讨论:

1.JVM类装载器的体系结构可以看作是树状结构。

2.父类装载器优先装载。在父类装载器装载失败的情况下再装载,如果都装载失败则抛出ClassNotFoundException或者NoClassDefFoundError异常。

那么我们的类在什么情况下被装载的呢?

2. 类如何被装载

在java2中,JVM是如何装载类的呢,可以分为两种类型,一种是隐式的类装载,一种式显式的类装载。

2.1 隐式的类装载

隐式的类装载是编码中最常用得方式:

A b = new A();

如果程序运行到这段代码时还没有A类,那么JVM会请求装载当前类的类装器来装载类。问题来了,我把代码弄得复杂一点点,但依旧没有任何难度,请思考JVM得装载次序:

package test;

Public class A{

public void static main(String args[]){

B b = new B();

}

}

class B{C c;}

class C{}

揭晓答案,类装载的次序为A->B,而类C根本不会被JVM理会,先不要惊讶,仔细想想,这不正

是我们最需要得到的结果。我们仔细了解一下JVM装载顺序。当使用Java

A命令运行A类时,JVM会首先要求类路径类装载器(AppClassLoader)装载A类,但是这时只装载A,不会装载A中出现的其他类(B类),接

着它会调用A中的main函数,直到运行语句b = new

B()时,JVM发现必须装载B类程序才能继续运行,于是类路径类装载器会去装载B类,虽然我们可以看到B中有有C类的声明,但是并不是实际的执行语句,

所以并不去装载C类,也就是说JVM按照运行时的有效执行语句,来决定是否需要装载新类,从而装载尽可能少的类,这一点和编译类是不相同的。

2.2 显式的类装载

使用显示的类装载方法很多,我们都装载类test.A为例。

使用Class类的forName方法。它可以指定装载器,也可以使用装载当前类的装载器。例如:

Class.forName("test.A");

它的效果和

Class.forName("test.A",true,this.getClass().getClassLoader());

是一样的。

使用类路径类装载装载.

ClassLoader.getSystemClassLoader().loadClass("test.A");

使用当前进程上下文的使用的类装载器进行装载,这种装载类的方法常常被有着复杂类装载体系结构的系统所使用。

Thread.currentThread().getContextClassLoader().loadClass("test.A")

使用自定义的类装载器装载类

public class MyClassLoader extends URLClassLoader{

public MyClassLoader() {

super(new URL[0]);

}

}

MyClassLoader myClassLoader = new MyClassLoader();

myClassLoader.loadClass("test.A");

MyClassLoader继承了URLClassLoader类,这是JDK核心包中的类装载器,在没有指定父类装载器的情况下,类路径类装载器就是它的父类装载器,MyClassLoader并没有增加类的查找范围,因此它和类路径装载器有相同的效果。

们已经知道Java的类装载器体系结构为树状,多个类装载器可以指定同一个类装载器作为自己的父类,每个子类装载器就是树状结构的一个分支,当然它们又可

以个有子类装载器类装载器,类装载器也可以没有父类装载器,这时Bootstrap类装载器将作为它的隐含父类,实际上Bootstrap类装载器是所有

类装载器的祖先,也是树状结构的根。这种树状体系结构,以及父类装载器优先的机制,为我们编写自定义的类装载器提供了便利,同时可以让程序按照我们希望的

方式进行类的装载。例如某个程序的类装载器体系结构图如下:

classloader.gif

图2:某个程序的类装载器的结构

释一下上面的图,ClassLoaderA为自定义的类装载器,它的父类装载器为类路径装载器,它有两个子类装载器ClassLoaderAA和

ClassLaderAB,ClassLoaderB为程序使用的另外一个类装载器,它没有父类装载器,但有一个子类装载器ClassLoaderBB。

你可能会说,见鬼,我的程序怎么会使用这么复杂的类装载器结构。为了进行下面的讨论,暂且委屈一下。

3. 奇怪的隔离性

们不难发现,图2中的类装载器AA和AB,

AB和BB,AA和B等等位于不同分支下,他们之间没有父子关系,我不知道如何定义这种关系,姑且称他们位于不同分支下。两个位于不同分支的类装载器具有

隔离性,这种隔离性使得在分别使用它们装载同一个类,也会在内存中出现两个Class类的实例。因为被具有隔离性的类装载器装载的类不会共享内存空间,使

得使用一个类装载器不可能完成的任务变得可以轻而易举,例如类的静态变量可能同时拥有多个值(虽然好像作用不大),因为就算是被装载类的同一静态变量,它

们也将被保存不同的内存空间,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很简单,编写自定义的类装载器。类装载器的这种隔离性在许多

大型的软件应用和服务程序得到了很好的应用。下面是同一个类静态变量为不同值的例子。

package test;

public class A {

public static void main( String[] args ) {

try {

//定义两个类装载器

MyClassLoader aa= new MyClassLoader();

MyClassLoader bb = new MyClassLoader();

//用类装载器aa装载testb.B类

Class clazz=aa.loadClass("testb. B");

Constructor constructor=

clazz.getConstructor(new Class[]{Integer.class});

Object object =

constructor.newInstance(new Object[]{new Integer(1)});

Method method =

clazz.getDeclaredMethod("printB",new Class[0]);

//用类装载器bb装载testb.B类

Class clazz2=bb.loadClass("testb. B");

Constructor constructor2 =

clazz2.getConstructor(new Class[]{Integer.class});

Object object2 =

constructor2.newInstance(new Object[]{new Integer(2)});

Method method2 =

clazz2.getDeclaredMethod("printB",new Class[0]);

//显示test.B中的静态变量的值

method.invoke( object,new Object[0]);

method2.invoke( object2,new Object[0]);

} catch ( Exception e ) {

e.printStackTrace();

}

}

}

//Class B 必须位于MyClassLoader的查找范围内,

//而不应该在MyClassLoader的父类装载器的查找范围内。

package testb;

public class B {

static int b ;

public B(Integer testb) {

b = testb.intValue();

}

public void printB() {

System.out.print("my static field b is ", b);

}

}

public class MyClassLoader extends URLClassLoader{

private static File file = new File("c://classes ");

//该路径存放着class B,但是没有class A

public MyClassLoader() {

super(getUrl());

}

public static URL[] getUrl() {

try {

return new URL[]{file.toURL()};

} catch ( MalformedURLException e ) {

return new URL[0];

}

}

}

程序的运行结果为:

my static field b is 1

my static field b is 2

程序的结果非常有意思,从编程者的角度,我们甚至可以把不在同一个分支的类装载器看作不同的java虚

拟机,因为它们彼此觉察不到对方的存在。程序在使用具有分支的类装载的体系结构时要非常小心,弄清楚每个类装载器的类查找范围,尽量避免父类装载器和子类

装载器的类查找范围中有相同类名的类(包括包名和类名),下面这个例子就是用来说明这种情况可能带来的问题。

假设有相同名字却不同版本的接口 A,

版本 1:

package test;

Intefer Same{ public String getVersion(); }

版本 2:

Package test;

Intefer Same{ public String getName(); }

接口A两个版本的实现:

版本1的实现

package test;

public class Same1Impl implements Same {

public String getVersion(){ return "A version 1";}

}

版本2的实现

public class Same 2Impl implements Same {

public String getName(){ return "A version 2";}

}

我们依然使用图2的类装载器结构,首先将版本1的Same和Same的实现类Same1Impl打成包

same1.jar,将版本2的Same和Same的实现类Same1Impl打成包same2.jar。现在,做这样的事情,把same1.jar放入

类装载器ClassLoaderA的类查找范围中,把same2.jar放入类装器ClassLoaderAB的类查找范围中。当你兴冲冲的运行下面这个

看似正确的程序。

实际上这个错误的是由父类载器优先装载的机制造成,当类装载器ClassLoaderAB

在装载Same2Impl类时发现必须装载接口test.Same,于是按规定请求父类装载器装载,父类装载器发现了版本1的test.Same接口并兴

冲冲的装载,但是却想不到Same2Impl所希望的是版本2 的test.Same,后面的事情可想而知了,异常被抛出。

我们很难责怪Java中暂时并没有提供区分版本的机制,如果使用了比较复杂的类装载器体系结构,在出现了某个包或者类的多个版本时,应特别注意。

掌握和灵活运用Java的类装载器的体系结构,对程序的系统设计,程序的实现,已经程序的调试,都有相当大的帮助。希望以上的内容能够对您有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/269676.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java用easyexcel实现读取excell表格内容

引入依赖 <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version></dependency><!-- https:…

java常见异常思维导图_21_异常_第21天(异常、企业面试题,思维导图下载)

今日内容介绍1、异常概述和继承体系2、异常原因以及处理方式3、运行时期异常4、方法重写的异常处理5、Throwable类常见方法6、自定义异常01异常的概述* A: 异常的概述* a:什么是异常* Java代码在运行时期发生的问题就是异常。* b:异常类* 在Java中&#xff0c;把异常信息封装成…

Spring中的InitializingBean接口的使用

InitializingBean接口为bean提供了初始化方法的方式&#xff0c;它只包括afterPropertiesSet方法&#xff0c;凡是继承该接口的类&#xff0c;在初始化bean的时候会执行该方法&#xff0c;具体说spring初始化bean之后执行该方法 配置文件 <bean id"userServices"…

Spring事务CallbackPreferringPlatformTransactionManager

CallbackPreferringPlatformTransactionManager if (txInfo.transactionAttribute ! null && txInfo.transactionAttribute.rollbackOn(ex)) {} 判断属性的默认值不为nul l且判断当前的异常是RuntimeException还是Error&#xff0c;会返回1&#xff0c;其他异常返回0…

@PostConstruct注解学习

PostConstruct注解好多人以为是Spring提供的。其实是Java自己的注解。 Java中该注解的说明&#xff1a;PostConstruct该注解被用来修饰一个非静态的void&#xff08;&#xff09;方法。被PostConstruct修饰的方法会在服务器加载Servlet的时候运行&#xff0c;并且只会被服务器…

idea for mac 控制台 mvn command not found

一&#xff1a;现在的IDEA 自带maven库maven库的地址如下图查看 二:完idea 需要配置maven库的环境变量才能找到该命令 Mac系统的环境变量&#xff0c;加载顺序为&#xff1a; a. /etc/profile b. /etc/paths c. ~/.bash_profile d. ~/.bash_login e. ~/.profile f. ~/.bashrc 其…

java天气预报webservice_webservice之实现天气预报

前通过传智的视频自学了webservice的基本使用&#xff0c;也了解到webservice就是一种跨编程语言和跨操作系统平台的远程调用技术。对于这些理论知识在这里也不再做过多的解释&#xff0c;本次主要就是记录与分享使用cxf 框架完成远程调用气象局提供的接口&#xff0c;来实现天…

CSS下拉菜单

例子&#xff1a; 鼠标移动到按钮上打开下拉菜单。&#xff08;在这里我将下拉菜单的内容的链接设置为百度首页&#xff09; 下拉菜单 菜单内容 1 菜单内容 2 菜单内容 3HTML 部分&#xff1a; 制作下拉菜单可以使用任何的 HTML元素来打开下拉菜单&#xff0c;如&#xff1a;&l…

maven跳过单元测试-maven.test.skip和skipTests的区别

第一种 -Dmaven.test.skiptrue&#xff0c;不执行测试用例&#xff0c;也不编译测试用例类。 一 使用maven.test.skip&#xff0c;不但跳过单元测试的运行&#xff0c;也跳过测试代码的编译。 mvn package -Dmaven.test.skiptrue<plugin> <groupId>org.apache.m…

linux下java命令行参数_Java调用Linux命令行

Java调用Linux命令行Java语言以其跨平台性和简易性而著称&#xff0c;在Java里面的lang包里(java.lang.Runtime)提供了一个允许Java程序与该程序所运行的环境交互的接口&#xff0c;这就是Runtime类&#xff0c;在Runtime类里提供了获取当前运行环境的接口。那么java怎么调用Li…

初识RPC概念

什么是RPC RPC 全称 Remote Procedure Call——远程过程调用。在学校学编程&#xff0c;我们写一个函数都是在本地调用就行了。但是在互联网公司&#xff0c;服务都是部署在不同服务器上的分布式系统&#xff0c;如何调用呢&#xff1f; RPC技术简单说就是为了解决远程调用服务…

Dubbo介绍

1:什是Dubbo 2&#xff1a;架构图 3:节点角色说明 4&#xff1a;调用关系说明

Mac idea使用Command + p 快捷键查看一个类的构造函数需要传入什么参数

Mac idea使用Command p 快捷键查看一个类的构造函数需要传入什么参数 如下图所示

TortoiseGit不同分支合并代码

现在有主分支master和分支day2.现在要把day2上的变更合并到主分支master上&#xff01; 1.首先切换到目标分支master上。 说明当前分支是master分支。 2.在master分支上查看提交记录,即show log一下。 3.切换到源分支上 4.选中你所有的提交&#xff0c;右键&#xff0c;Cherry …

java jdk 类加载机制_JDK源码阅读之类加载

java类加载类的生命周期(类加载过程)LLIUUVPR加载(Loading)链接(Linking)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸载(Unloading) 类类加载器种类BootstrapClassLoader&#xff1a;C编写&#xff0c;负责加载java核心类库Launc…

2017.4.11 AM

练恋有词U28单元单词及应用 转载于:https://www.cnblogs.com/bgd140201219/p/6696472.html

volatile不具备原子性

1、理解原子性&#xff1a; 上面说到volatile不具备原子性&#xff0c;那么原子性到底是什么呢&#xff1f;先看如下代码 public class TestVolatile {public static void main(String[] args) {AtomicDemo atomicDemo new AtomicDemo();for (int x 0; x < 10; x) {new Th…