JAVA虚拟机设计团队有意把类加载阶段中的“ 通过一个类的全限定名来获取描述该类的二进制字节流 ”这个动作放到java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”。
类加载器用于实现类的加载动作,对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。
在比较两个类是否“相等”时,如果两个类来源于同一个 class文件且被同一个Java虚拟机加载,但只要加载它们的类加载器不同,那这两个类就必定不相等。因此,比较两个类是否“相 等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。
类加载器的分类从JVM角度来看,只存在两种不同的类加载器:
启动类加载器 (Bootstrap ClassLoader),这个类加载器使用C 语言实现,是虚拟机自身的一部分;其他所有类加载器 :这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。但从我们开发者的角度来看,可以把类加载器细化如下:
启动类加载器(Bootstrap Class Loader) :即上面所述的类加载器。这个类加载器负责加载存放在 <Java_HOME>lib 目录 ,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时, 如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可。扩展类加载器(Extension Class Loader) :这个类加载器是在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现的。它负责加载<JAVA_HOME>libext目录中,或者被java.ext.dirs系统变量所 指定的路径中所有的类库。这是一种Java系统类库的扩 展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。由于扩扩展类加载器是由Java代码实现 的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。应用程序类加载器(Application Class Loader) :这个类加载器由 sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem- ClassLoader()方法的返回值,所以有些场合中也称它为“ 系统类加载器 ”。它负责加载用户类路径 (ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有 自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。用户自定义类加载器(User Class Loader)双亲委派模型Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的就是 双亲委派模型 。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加 载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的 加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请 求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合关系来复用父加载器的代码。
双亲委派模型源码双亲委派模型实现位于 java.lang.ClassLoader 中的 loadClass(java.lang.String, boolean) 方法中,具体如下:
<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }</pre>可以解释如下:先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的 loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败, 抛出ClassNotFoundException异常的话,才调用自己的findClass()方法尝试进行加载。
沙箱安全机制沙箱安全机制就是将java代码限定在虚拟机特定的运行范围中,并且严格限制代码对本地系统资源的访问,通过这样的措施来保证对代码的有效隔离,防止对系统造成破坏。
例如:
我们自定义一个String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中javalang string.class)。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。
自定义类加载器在Java的日常应用程序开发中,类的加载几乎是由这三种类加载器互相配合来完成加载的,如果用户认为有必要,还可以加入自定义的类加载器来进行拓展,典型的如增加除了磁盘位置之外的Class文件来源,或者通过类 加载器实现类的隔离、重载等功能。
为什么要自定义类加载器自定义类加载器可以实现以下功能:
隔离加载类修改类加载的方式扩展加载源防止源码泄漏实现方式通过继承抽象类java.lang.classLoader类的方式,实现自己的类加载器,以满足一些特殊的需求。在JDK1.2之前,在自定义类加载器时,总会去继承classLoader类并重写loadclass ()方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadclass()方法,而是建议把自定义的类加载逻辑写在findclass ()方法中。在编写自定义类加载器时,如果没有太过于复杂的需求可以直接继承URLClassLoader类,这样就可以避免自己去编写findclass ()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。类的主动使用和被动使用类的主动使用和被动使用区别在于是否会导致类的初始化,导致类的初始化为类的主动适用,不导致类的初始化为类的被动使用。
主动使用有以下7中情况:
创建类的实例访问某个类或接口的静态变量,或者对该静态变量赋值调用类的静态方法反射(比如: Class.forName ( “com.atguigu . Test” ) )初始化一个类的子类Java虚拟机启动时被标明为启动类的类JDK 7开始提供的动态语言支持:java . lang . invoke.MethodHandle实例的解析结果REF getstatic、REF_putstatic、REF_invokestatic句柄对应的类没有初始化,则初始化除以上7种情况外都是被动使用。
以上内容为【带你深入理解java虚拟机- 类加载器与双亲委派模型(java学生通讯录源代码)】的相关内容。
本文内容来自用户上传并发布或网络新闻客户端自媒体,本站点仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请联系删除。