Java内存管理-掌握虚拟机类加载器(五)

勿在流沙筑高台,出来混迟早要还的。

做一个积极的人

编码、改bug、提升自己

我有一个乐园,面向编程,春暖花开!

上一篇介绍虚拟机类加载机制,讲解了类加载机制中的三个阶段,分别是:加载、连接(验证、准备、解析)、初始化 ,知道了类加载的机制。下面我们就要知道类到底是通过什么方式加载到内存中的,也就是本文要介绍的类加载器,类加载器就是加载类的信息到内存中

本文地图 :

一、什么是类加载器(ClassLoader)

虚拟机设计团队把类加载阶段中的”通过一个类的全限定名来获取描述此类的二进制字节流“这个动作是放到Java虚拟机外部去实现的,以便让应用程序自己决定如何去获取所需的类,实现这个动作的代码模块称为”类加载器“。

说简单一点:类加载器可以把类加载到Java虚拟机中,我们可以使用Java虚拟机自带的类加载器,也可以自定义实现自己的类加载器(自定义类加载器会在后面文章进行讲解)。

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用远不限于类加载阶段。对于任意的一个类,都需要由加载它的类加载器和这个类本身一同确立起在Java虚拟机中的唯一性。每个类加载器都拥有一个独立的类命名空间(命名空间下面单独会介绍)。

说简单一点:比较两个类是否”相等“,只有在这两个类是由同一个类加载器加载的前提在才有意义,否则,即使这两个类源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类必定不相等。这里所指的“相等”包括代表类的Class对象的equal方法、isAssignableFrom()、isInstance()方法及instance关键字返回的结果。

如上图,此时虽然是同一个Person.class ,但是被不同的类加载器加载到Java虚拟机,那么加载后的这两个 Person类是不”相等“,因为它们分别在两个不同的命名空间中。

注:如果上面两个类的是否”相等“比较你没看懂的话,后面自定义类加载器的时候我们会演示一下这个例子,进行简单说明。

二、类加载器分类

一张图看懂类加载器分类:

上图的类加载器主要分为四类:

  • Bootstrap ClassLoader : 启动类加载器
  • Extension ClassLoader : 扩展类加载器
  • Application ClassLoader :应用程序类加载器
  • User ClassLoader :自定义类加载器

从虚拟机角度分析,类加载器分为两类: 一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种是其他类加载器,这些类加载器是Java语言实现的,独立于虚拟机之外,都继承自抽象类java.lang.ClassLoader。

public class ClassLoaderDemo {

    public static void main(String[] args) {

        // ClassLoaderDemo 的类加载器
        System.out.println(ClassLoaderDemo.class.getClassLoader());

        // 打印每个 ClassLoader的父加载器
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
        while (classLoader != null){
            System.out.println(classLoader);
            classLoader = classLoader.getParent();
        }
        System.out.println(classLoader);
    }
}
--- 打印结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null

启动类加载器(根加载器)

主要负责加载存放在JAVA_HOME/jre/lib/rt.jar里面所有的class文件,或者被-Xbootclasspath参数所指定路径中以rt.jar命名的文件。启动类加载器无法被Java程序直接引用,如果在编写自定义类加载时,需要把加载的请求委派给启动类加载器,那么直接使用null代替即可。

例如:

MyClassLoader myClassLoader= new MyClassLoader(null); //父加载器使用启动类加载器
public MyClassLoader(ClassLoader parent){
    super(parent);//指定该类加载器的父类加载器
}

扩展类加载器

这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载AVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。

应用程序类加载器

这个加载器由sun.misc.Launcher$AppClassLoader实现,它负责加载classpath对应的jar及目录。如果应用程序没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

自定义类加载器

用户定制自己的类加载器,继承ClassLoader,根据自己的需要进行设计和实现,比如需要从指定的路径下读取class文件进行加载等。

知道上面这些内容,你就应该联想到为什么在学习Java的时候首先要配置Java环境变量了。这就是知其所以然的过程。

思考:这么多类加载器,那么如何保证一个类不被重复加载,只被加载一次呢?

从JDK1.2版本开始,类加载过程中采用了双亲委派模型(父亲委派机制),这种机制能更好的保证Java平台的安全,在此委托机制中,除了Java虚拟机自带的根类加载(根加载器最顶级,无父加载器)以外,其余的类加载器都有且只有一个父加载器。双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求(它搜索的范围中没有找打所需的类)时,子加载器才会尝试自己去加载。

如下示意图:

在说明一下: 加载一个类的时候。首先自定义类加载类器先会委派给应用程序类加载器(Application ClassLoader),应用程序类加载器会委派给扩展类加载器(Extension ClassLoader),扩展类加载器会委派给启动类加载器(Bootstrap ClassLoader)去加载,这时候如果启动类加载器加载成功,则加载结束。如果加载失败,则交给扩展类加载器去加载,如果扩展类加载器加载成功,则加载结束。如果加载失败,则交给应用程序类加载器,如果应用程序类加载器加载成功,则加载结束。如果加载失败,则交给自定义类加载器。如果自定义类加载器加载成功,则加载结束。否则加载失败,会报ClassNotFoundExecption异常,结束。

每个类加载器都有自己的管辖范围(命名空间),并在自己的管辖范围做好自己的事情。

双亲委派模型还有一个优点是能提供软件系统的安全性,在这个机制下,用户自定义的类加载器不可能加载应该由父类加载器加载的可靠类,从而防止不可靠甚至是恶意的代码代替父类加载器加载可靠代码。如 java.land.Object 类总是由启动类加载器进行加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.land.Object 类。(每一个看似简单的设计背后,都是大师们智慧的结晶)



tips: 类加载器命名空间

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父类加载器所加载的类组成。在同一个命名空间中,不会出现两个全类名(包名+类名)完全一样的类;在不同的命名空间中,有可能出现全类名相同的两个类。

一定要注意是:有可能出现全类名一样的,就如 Loader1 中自定义类加载器可以加载如 com.aflyun.HelloJVM.java , Loader2 中自定义类加载器也可以加载如 com.aflyun.HelloJVM.java 的类,两个互不影响,并且比较两个类的话,是不”相等“的。并且不同命名空间中类加载器加载的类,不能访问其他类加载器包中的包可见(即默认访问级别)成员。

三、总结

本文主要介绍了什么是类加载器以及类加载器的分类 ,让大家对类加载器相关的知识有一个整体的认识,这样我们也知道了哪些类加载器加载什么地方的数据。为我们后面对类加载器源码和分析以及实现自定义类加载器做好铺垫。预告:下一篇文章讲一下类加载器的源码分析和设计模式,以及实现一个自定义类加载器!

四、参考资料

《深入理解Java虚拟机》

推荐阅读

Java的线程安全、单例模式、JVM内存结构等知识梳理
Java内存管理-程序运行过程(一)
Java内存管理-初始JVM和JVM启动流程(二)
Java内存管理-JVM内存模型以及JDK7和JDK内存模型对比总结(三)



谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!



不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : http://blog.csdn.net/u010648555

愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人

© 每天都在变得更好的阿飞云

原文地址:https://www.cnblogs.com/aflyun/p/10597657.html

时间: 2024-10-16 02:23:28

Java内存管理-掌握虚拟机类加载器(五)的相关文章

Java内存管理-掌握自定义类加载器的实现(七)

勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分析了ClassLoader的类加载相关的核心源码,也简单介绍了ClassLoader的设计思想,读源码相对来说是比较枯燥的,还是这个是必须要走的过程,学习源码中的一些思想,一些精髓,看一下大神级人物是怎么写出那么牛逼的代码.我们能够从中学到一点点东西,那也是一种进步和成长了.本文基于上一篇文章内容,手把手写一个自定义类加载器,并且通过一些简单的案例(场景)让我们更加细致和静距

Java内存区域与虚拟机类加载机制

一.Java运行时数据区域 1.程序计数器 "线程私有"的内存,是一个较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器.Java虚拟机规范中唯一一个没有OutOfMemoryError情况的区域. 字节码解释器工作时就说通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支.循环.跳转.异常处理.线程恢复等基础功能都需要依赖这个计数器来完成. 2.Java虚拟机栈 Java 虚拟栈,线程私有的,它的生命周期与线程相同.每个方法在执行的同时都会创建一个栈帧用于存储局部变

详解Java内存区域?虚拟机类加载机制?

一.Java运行时数据区域 1.程序计数器 "线程私有"的内存,是一个较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器.Java虚拟机规范中唯一一个没有OutOfMemoryError情况的区域. 字节码解释器工作时就说通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支.循环.跳转.异常处理.线程恢复等基础功能都需要依赖这个计数器来完成. 2.Java堆(Java Heap) Java堆是Java虚拟机所管理的内存中最大的一块.在虚拟机启动时创建,此内存区域的唯一

深入java虚拟机-类加载器

此系列为深入java虚拟机(周志明著)学习笔记 通过一个类的全限定名来获取描述此类的二进制字节流的代码模块称为类加载器. 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性.通俗的理解:比较两个类是否"相等",只有在这两个类是同一类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载她们的类加载器不同,那这两个类必定不相等.这里的"相等",包括类的Class对象的equals()

java jvm虚拟机类加载器

在Java中任意一个类都是由这个类本身和加载这个类的类加载器来确定这个类在JVM中的唯一性. 类加载器 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现, 以便让应用程序自己决定如何去获取所需要的类. 实现这个动作的代码模块称为“类加载器”. 类与类加载器 类加载器虽然只用于实现类的加载动作, 但它在Java程序中起到的作用却远远不限于类加载阶段. 对于任意一个类, 都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟

java内存管理机制

JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间.释放 :对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作.但同时,它也加重了JVM的工作.因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请.引用.被引用.赋值等,GC都需要进行监控. 2. 

Java高新技术第一篇:类加载器详解

首先来了解一下字节码和class文件的区别: 我们知道,新建一个Java对象的时候,JVM要将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath(就是我们新建Java工程的bin目录下)指定的目录下的.class文件,类加载需要将.class文件导入到硬盘中,经过一些处理之后变成字节码在加载到内存中. 下面来看一下简单的例子: [java] view plain copy package com.loadclass.demo; import java.util.Dat

[转载] Java高新技术第一篇:类加载器详解

本文转载自: http://blog.csdn.net/jiangwei0910410003/article/details/17733153 首先来了解一下字节码和class文件的区别: 我们知道,新建一个Java对象的时候,JVM要将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath(就是我们新建Java工程的bin目录下)指定的目录下的.class文件,类加载需要将.class文件导入到硬盘中,经过一些处理之后变成字节码在加载到内存中. 下面来看一下简单的例子:

揭开Java内存管理的面纱

前言 相对于C.C++这些高性能语言,Java有着让此类程序员羡慕的功能:内存自动管理.似乎这样,Java程序员不用再关心内存,也不用去了解相关知识.但结果真的是这样吗?特别对于我们这种Android程序员来说,对内存可是吃得死死的,一旦出现较为复杂的内存泄露和溢出方面的问题,简直就是噩梦.因此,对Java内存管理有个大体的了解似乎已经成为一个合格的Android程序员必备的技能,就算是新进的Kotlin同样是基于JVM的.不如趁此机会,大家一起来揭开它的面纱. 对象 Java是一门面向对象的编