Java类装载体系中的隔离性

Java类装载体系中的隔离性优化——保护你的代码 在Java开发中,类装载器负责加载和连接类的定义,并将其转换为可执行的代码。类装载体系的设计对于应用程序的安全性和性能至关重要。其中,隔离性是一个重要的概念,它确保不同类加载器加载的类之间相互隔离,防止类的意外替换和访问权限的滥用。本文将介绍如何优化Java类装载体系的隔离性,保护你的代码免受潜在的风险。 阅读详情

作者:盛戈歆

作者简介

盛戈歆,软件工程师,你可以通过shenggexin@topwaver.com与他联系。

正文

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得装载次序:

 
 1  package  test;
 2  Public  class  A{
 3       public   void   static  main(String args[]){
 4          B b =  new  B();
 5      }
 6  }
 7 
 8  class  B{C c;}
 9 
10  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 " )

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

1  public   class  MyClassLoader  extends  URLClassLoader{
2  public  MyClassLoader() {
3           super ( new  URL[ 0 ]);
4      }
5  }
6  MyClassLoader myClassLoader  =   new  MyClassLoader();
7  myClassLoader.loadClass( " test.A " );

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

我们已经知道Java的类装载器体系结构为树状,多个类装载器可以指定同一个类装载器作为自己的父类,每个子类装载器就是树状结构的一个分支,当然它们又可以个有子类装载器类装载器,类装载器也可以没有父类装载器,这时Bootstrap类装载器将作为它的隐含父类,实际上Bootstrap类装载器是所有类装载器的祖先,也是树状结构的根。这种树状体系结构,以及父类装载器优先的机制,为我们编写自定义的类装载器提供了便利,同时可以让程序按照我们希望的方式进行类的装载。例如某个程序的类装载器体系结构图如下:

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

解释一下上面的图,ClassLoaderA为自定义的类装载器,它的父类装载器为类路径装载器,它有两个子类装载器ClassLoaderAA和ClassLaderAB,ClassLoaderB为程序使用的另外一个类装载器,它没有父类装载器,但有一个子类装载器ClassLoaderBB。你可能会说,见鬼,我的程序怎么会使用这么复杂的类装载器结构。为了进行下面的讨论,暂且委屈一下。

3. 奇怪的隔离性

我们不难发现,图2中的类装载器AA和AB, AB和BB,AA和B等等位于不同分支下,他们之间没有父子关系,我不知道如何定义这种关系,姑且称他们位于不同分支下。两个位于不同分支的类装载器具有隔离性,这种隔离性使得在分别使用它们装载同一个类,也会在内存中出现两个Class类的实例。因为被具有隔离性的类装载器装载的类不会共享内存空间,使得使用一个类装载器不可能完成的任务变得可以轻而易举,例如类的静态变量可能同时拥有多个值(虽然好像作用不大),因为就算是被装载类的同一静态变量,它们也将被保存不同的内存空间,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很简单,编写自定义的类装载器。类装载器的这种隔离性在许多大型的软件应用和服务程序得到了很好的应用。下面是同一个类静态变量为不同值的例子。

 1  package  test;
 2  public   class  A {
 3     public   static   void  main( String[] args ) {
 4       try  {
 5         // 定义两个类装载器
 6        MyClassLoader aa =   new  MyClassLoader();
 7        MyClassLoader bb  =   new  MyClassLoader();
 8 
 9         // 用类装载器aa装载testb.B类
10        Class clazz = aa.loadClass( " testb. B " );
11        Constructor constructor =  
12          clazz.getConstructor( new  Class[]{Integer. class });
13        Object object  =  
14          constructor.newInstance( new  Object[]{ new  Integer( 1 )});
15        Method method  =  
16          clazz.getDeclaredMethod( " printB " , new  Class[ 0 ]);
17 
18         // 用类装载器bb装载testb.B类
19        Class clazz2 = bb.loadClass( " testb. B " );
20        Constructor constructor2  =  
21          clazz2.getConstructor( new  Class[]{Integer. class });
22        Object object2  =  
23          constructor2.newInstance( new  Object[]{ new  Integer( 2 )});
24        Method method2  =  
25          clazz2.getDeclaredMethod( " printB " , new  Class[ 0 ]);
26 
27         // 显示test.B中的静态变量的值 
28        method.invoke( object, new  Object[ 0 ]);
29        method2.invoke( object2, new  Object[ 0 ]);
30      }  catch  ( Exception e ) {
31        e.printStackTrace();
32      }
33    }
34  }
35 
36 
37  // Class B 必须位于MyClassLoader的查找范围内,
38  // 而不应该在MyClassLoader的父类装载器的查找范围内。
39  package  testb;
40  public   class  B {
41       static   int  b ;
42 
43       public  B(Integer testb) {
44          b  =  testb.intValue();
45      }
46 
47       public   void  printB() {
48          System.out.print( " my static field b is  " , b);
49      }
50  }
51 
52 
53  public   class  MyClassLoader  extends  URLClassLoader{
54     private   static  File file  =   new  File( " c://classes  " );
55     // 该路径存放着class B,但是没有class A
56 
57     public  MyClassLoader() {
58       super (getUrl());
59    }
60 
61     public   static  URL[] getUrl() {
62       try  {
63         return   new  URL[]{file.toURL()};
64      }  catch  ( MalformedURLException e ) {
65         return   new  URL[ 0 ];
66      }
67    }
68  }


程序的运行结果为:

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://gceclub.sun.com.cn/yuanchuang/week-9/classloader.html

VS2022离线环境下的NuGet包管理:从本地源到依赖项全解析 本文详细解析了在VS2022离线环境下如何高效管理NuGet包,包括搭建本地NuGet源的两种方案(本地文件夹源和私有NuGet服务器)、获取离线包的三大渠道以及依赖项处理的实战技巧。通过自动化脚本和依赖树分析工具,帮助开发者解决离线环境中的依赖项黑洞问题,提升开发效率。 阅读详情

相关推荐

手把手教你用Wireshark抓包分析BLE广播包:从PDU结构到AD Type实战解析

本文详细介绍了如何使用Wireshark工具抓取并分析BLE广播包,从PDU结构到AD Type的实战解析。通过硬件设备选择、软件配置、协议栈解析及高级故障排查,帮助开发者深入理解蓝牙低功耗通信机制,提升物联网设备调试效率。

weixin_30482383的博客 417

ClassNotFoundException解决方案总结

相信很多初学Android的会遇到这个问题,在此做一个总结,方便日后查阅。 1,首先说一下异常 ClassNotFoundException:类找不到异常。首先看你的logcat中是否显示Caused by : java.lang.ClassNotFoundExcetion.. 2,产生条件 什么时候会抛出classnotfoundException异常呢?这涉及到底层的API。

fanfan 15万+

【异常】解决JDK21升级中的SecurityException: JCE无法验证BC提供者问题

本文详细解析了JDK21升级中常见的SecurityException: JCE无法验证BC提供者问题的根源与解决方案。核心在于JDK21强化了JCE安全验证,导致签名不合规的老版本Bouncy Castle库被拒绝。文章提供了通过Maven依赖树精准定位、排除旧版本并引入兼容的bcprov-jdk18on新库的完整步骤,以及清理缓存等实战技巧,帮助开发者彻底解决此兼容性问题。

weixin_29197051的博客 490

关于启动报错——ClassNotFoundException异常

原因:我认为“ClassNotFoundException”异常是因为在指定的存放class文件夹下未找到相关的class文件导致。 在很多时候启动项目的时候,莫名奇妙会报“ClassNotFoundException”的错误,下面我自己来总结下自己知道的情况: 1.因为没有引入相关的jar包...

chongyunyuan4177的博客 1592

使用electron静默打印

使用electron静默打印 1.使用electron打印的理由 很多情况下程序中使用的打印都是用户无感知的。并且想要灵活的控制打印内容,往往需要借助打印机给我们提供的api再进行开发,这种开发方式非常繁琐,并且开发难度较大。 electron提供的打印api可以非常灵活的控制打印设置的显示,并且可以通过html来书写打印内容。 2.api ele...

w1366352655的博客 945

内存设计指南:01 Xilinx DDR4 design guide解析

最近有很多网友咨询FPGA DDR4为什么速率总是上不去的问题,发现他们的设计确实很随意,都没有遵守一些手册的基本要求,所以今天和大家一起重新读一读DDR4 design guide,看看哪些要求是一定不能忽略的。首次设计一款新的芯片或者内存方案时,可能会无从下手。对于内存设计,官方指导首推JEDEC规范,每一代内存推出时,在JEDEC官网均能找到对应的规范文档、各种内存形式的说明文档如DDR4-JESD79、DDR4 SDRAM UDIMM Design Specification以及内存条DEMO等。

halee_00的博客 3302

java.lang.ClassNotFoundException: com.mysql.jdbc.Driver解决方法

Exception in thread "main" java.lang.ClassNotFoundException: com.mysql.jdbc.Driver         at java.net.URLClassLoader.findClass(Unknown Source)         at java.lang.ClassLoader.loadClass(Unknown Sourc...

Windsearcher的博客 2263

自动化学报latex模板——ctex2.4.6和ctex2.9.2直接使用步骤

自动化学报latex模板——ctex2.4.6和ctex2.9.2直接使用步骤 1.背景及问题 最近在使用自动化学报latex模板写作业时,一只对latex搞不清楚,网上关于各种版本的latex下载了一堆(比如ctex中文套装方案的各个版本及texlive+texstudio方案,中间还升级了miktex,windet等),发现都未能成功编译出来自动化学报的latex模板,反正开始头很大!!!最后终于没忍住打开了: 自动化学报官网下载模板链接,因为我就要用latex模板(不能服输!)找到的解决方案是:使用

QIANFANGshiwo的博客 7709

IDEA中项目报ClassNotFoundException异常的解决方案

idea中Maven项目或者Web项目出现 java.lang.ClassNotFoundException 异常的解决方法

weixin_43631089的博客 1万+

【Unity Shader】2D模型 3D模型混用的穿模问题(Spine在3D场景中出现前后穿模问题的解决办法)

问题描述: 在3D场景中使用Spine模型(2D), 因为Spine需要像Billboard类似的处理,永远是垂直与相机的, 所以一般情况下2D模型能正常显示且前后的阻挡关系也是没有问题的,如下图所示,一切都很美好.(这里用一个四边形,图中的福字表示2D模型) 可是如果后面蓝色的格子比较高,或者2D角色本身也比较高,那么就会出现模型穿插的问题,如下图所示: 如果吧2D模型竖立起来,前后关系肯定是没有问题的,所以也能避免模型穿插的问题,可是就没有Billboard效果,圆形的福字已经被压扁了,如下图:

Vitens的专栏 4590

短信验证码逻辑漏洞学习——各种绕过姿势技巧

短信验证码逻辑漏洞学习——各种绕过姿势技巧

记录并分享学习安全的知识点.. 2556

【转载】Java类装载体系中的隔离性

Java类装载体系中的隔离性作者:盛戈歆作者简介盛戈歆,软件工程师,你可以通过shenggexin@topwaver.com与他联系。正文:Java中类的查找与装载出现的问题总是会时不时出现在Java程序员面前,这并不是什么丢脸的事情,相信没有一个Java程序员没遇到过ClassNotException,因此不要为被人瞅见自己也犯这样的错误而觉得不自然,但是在如果出现了ClassNotFoundE

starsinsky's JAVABLOG 981

2026 MCM Problem A:智能手机电池连续时间建模——完整解题思路附第一问解题代码

摘要:本文针对2026年MCM A题"智能手机电池连续时间建模"问题,提出基于能量守恒原理的微分方程模型。通过建立以SOC(t)为状态变量的动态系统,将屏幕亮度、处理器负载等主要耗电因素纳入统一功率框架,实现了不同使用场景下的电量变化精确描述。研究表明,屏幕亮度和CPU负载对续航时间影响最大,温度与后台任务会产生放大效应。该模型具有物理可解释性和应用扩展性,为手机电源管理提供了理论依据。(150字)

一键难忘的博客 769

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

作者简介正文Java中类的查找与装载出现的问题总是会时不时出现在Java程序员面前,这并不是什么丢脸的事情,相信没有一个Java程序员没遇到过ClassNotException,因此不要为被人瞅见自己也犯这样的错误而觉得不自然,但是在如果出现了ClassNotFoundException后异常后一脸的茫然,那我想你该了解一下java的类装载的体制了,同时为了进行下面的关于类装载器之间的隔离性的讨论...

weixin_29957761的博客 325

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Java类装载体系中隔离性

目标链接:http://www.oschina.net/question/30362_3895 Java中类的查找与装载出现的问题总是会时不时出现在Java程序员面前,这并不是什么丢脸的事情,相信没有一个 Java程序员没遇到过ClassNotException,因此不要为被人瞅见自己也犯这样的错误而觉得不自然,但是在如果出现了 ClassNotFoundException后异常后一脸的

liuzx32的专栏 648

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

上一篇: 关于Applet的一个小收获
下一篇: JAVA IO流的几种基本操作
JustForFly
博客等级 码龄20年 18粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值