Java 的类加载器-双亲委托机制

解释内存中的栈 (stack) 、堆 (heap) 和方法区 (method area) 的用法

通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用 JVM 中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为 Eden、Survivor(又可分为 From Survivor 和 To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT 编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的 100、"hello"和常量都是放在常量池中,常量池是方法区的一部分。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过 JVM 的启动参数来进行调整,栈空间用光了会引发 StackOverflowError,而堆和常
量池空间不足则会引发 OutOfMemoryError。
String str = new String(“hello”);
上面的语句中变量 str 放在栈上,用 new 创建出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。

1. Java的类加载器的种类都有哪些?

1、根类加载器(Bootstrap) --C++写的 ,看不到源码
2、扩展类加载器(Extension) --加载位置 :jre\lib\ext中
3、系统(应用)类加载器(System\App) --加载位置 :classpath中
4、自定义加载器(必须继承ClassLoader)

2. 类什么时候被初始化?

1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName(“com.lyj.load”))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
只有这6中情况才会导致类的类的初始化。
类的初始化步骤:
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

3. Java类加载体系之ClassLoader双亲委托机制

java 是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是:
1) 类加载体系
2) .class文件检验器
3) 内置于Java虚拟机(及语言)的安全特性
4) 安全管理器及Java API
主要讲解类的加载体系:
java程序中的 .java文件编译完会生成 .class文件,而 .class文件就是通过被称为类加载器的ClassLoader加载的,而ClassLoder在加载过程中会使用“双亲委派机制”来加载 .class文件,先上图:
在这里插入图片描述
BootStrapClassLoader : 启 动 类 加 载 器 , 该 ClassLoader 是 jvm 在 启 动 时 创 建 的 , 用 于 加载 J A V A H O M E JAVA_HOME JAVAHOME/jre/lib下面的类库(或者通过参数-Xbootclasspath指定)。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不能直接通过引用进行操作。
ExtClassLoader:扩展类加载器,该ClassLoader是在sun.misc.Launcher里作为一个内部类ExtClassLoader定义的(即sun.misc.Launcher$ExtClassLoader),ExtClassLoader 会加载 $JAVA_HOME/jre/lib/ext 下的类库(或者通过参数-Djava.ext.dirs指定)。

AppClassLoader:应用程序类加载器,该 ClassLoader 同样是在 sun.misc.Launcher 里作为一个内部类AppClassLoader定义的(即 sun.misc.Launcher A p p C l a s s L o a d e r ) , A p p C l a s s L o a d e r 会 加 载 j a v a 环 境 变 量 C L A S S P A T H 所 指 定 的 路 径 下 的 类 库 , 而 C L A S S P A T H 所 指 定 的 路 径 可 以 通 过 S y s t e m . g e t P r o p e r t y ( " j a v a . c l a s s . p a t h " ) 获 取 ; 当 然 , 该 变 量 也 可 以 覆 盖 , 可 以 使 用 参 数 − c p , 例 如 : j a v a − c p 路 径 ( 可 以 指 定 要 执 行 的 c l a s s 目 录 ) 。 C u s t o m C l a s s L o a d e r : 自 定 义 类 加 载 器 , 该 C l a s s L o a d e r 是 指 我 们 自 定 义 的 C l a s s L o a d e r , 比 如 t o m c a t 的 S t a n d a r d C l a s s L o a d e r 属 于 这 一 类 ; 当 然 , 大 部 分 情 况 下 使 用 A p p C l a s s L o a d e r 就 足 够 了 。 前 面 谈 到 了 C l a s s L o a d e r 的 几 类 加 载 器 , 而 C l a s s L o a d e r 使 用 双 亲 委 派 机 制 来 加 载 c l a s s 文 件 的 。 C l a s s L o a d e r 的 双 亲 委 派 机 制 是 这 样 的 ( 这 里 先 忽 略 掉 自 定 义 类 加 载 器 C u s t o m C l a s s L o a d e r ) : 1 ) 当 A p p C l a s s L o a d e r 加 载 一 个 c l a s s 时 , 它 首 先 不 会 自 己 去 尝 试 加 载 这 个 类 , 而 是 把 类 加 载 请 求 委 派 给 父 类 加 载 器 E x t C l a s s L o a d e r 去 完 成 。 2 ) 当 E x t C l a s s L o a d e r 加 载 一 个 c l a s s 时 , 它 首 先 也 不 会 自 己 去 尝 试 加 载 这 个 类 , 而 是 把 类 加 载 请 求 委 派 给 B o o t S t r a p C l a s s L o a d e r 去 完 成 。 3 ) 如 果 B o o t S t r a p C l a s s L o a d e r 加 载 失 败 ( 例 如 在 AppClassLoader),AppClassLoader会加载java环境变量CLASSPATH 所 指 定 的 路 径 下 的 类 库 , 而 CLASSPATH 所 指 定 的 路 径 可 以 通 过System.getProperty("java.class.path")获取;当然,该变量也可以覆盖,可以使用参数-cp,例如:java -cp 路径 (可以指定要执行的class目录)。 CustomClassLoader:自定义类加载器,该ClassLoader是指我们自定义的ClassLoader,比如tomcat的StandardClassLoader属于这一类;当然,大部分情况下使用AppClassLoader就足够了。 前面谈到了ClassLoader的几类加载器,而ClassLoader使用双亲委派机制来加载class文件的。ClassLoader的双亲委派机制是这样的(这里先忽略掉自定义类加载器CustomClassLoader): 1)当 AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。 2)当 ExtClassLoader 加载一个 class 时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。 3)如果 BootStrapClassLoader 加载失败(例如在 AppClassLoaderAppClassLoaderjavaCLASSPATHCLASSPATHSystem.getProperty("java.class.path")使cpjavacpclassCustomClassLoaderClassLoaderClassLoadertomcatStandardClassLoader使AppClassLoaderClassLoaderClassLoader使classClassLoaderCustomClassLoader1AppClassLoaderclassExtClassLoader2ExtClassLoaderclassBootStrapClassLoader3BootStrapClassLoaderJAVA_HOME$/jre/lib 里未查找到该 class),会使用ExtClassLoader来尝试加载;
4)若 ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
下面贴下ClassLoader的loadClass(String name, boolean resolve)的源码:
在这里插入图片描述
代码很明朗:首先找缓存(findLoadedClass),没有的话就判断有没有parent,有的话就用parent来递归的loadClass,然而ExtClassLoader并没有设置parent,则会通过findBootstrapClassOrNull来加载class,而
findBootstrapClassOrNull则会通过JNI方法”private native Class findBootstrapClass(String name)“来使用BootStrapClassLoader来加载class。
然后如果 parent 未找到class,则会调用 findClass 来加载 class,findClass 是一个 protected 的空方法,可以覆盖它以便自定义class加载过程。
另外,虽然 ClassLoader 加载类是使用 loadClass 方法,但是鼓励用 ClassLoader 的子类重写 findClass(String),而不是重写loadClass,这样就不会覆盖了类加载默认的双亲委派机制。

  • 双亲委派托机制为什么安全
    举个例子,ClassLoader加载的class文件来源很多,比如编译器编译生成的class、或者网络下载的字节码。而一些来源的 class 文件是不可靠的,比如我可以自定义一个 java.lang.Integer 类来覆盖 jdk 中默认的 Integer
    类,例如下面这样:
    在这里插入图片描述
    初始化这个Integer的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话该Integer类永远不会被调用,以为委托BootStrapClassLoader加载后会加载JDK中的Integer类而不会加载自定义的这个,可以看下下面这测试个用例:
    在这里插入图片描述
    执行时JVM并未在new Integer(1)时退出,说明未使用自定义的Integer,于是就保证了安全性。

4. 描述一下JVM加载class

JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的 Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化,包括:
如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
如果类中存在初始化语句,就依次执行这些初始化语句。类的加载是由类加载器完成的,类加载器包括:根加载器
(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader
的子类)。
从 Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java 程序提供对 Bootstrap 的引用。
下面是关于几个类加载器的说明:
• Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);
• Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap;
• System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的类加载器。它从环境变量classpath 或者系统属性 java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。

5. 获得一个类对象有哪些方式?

类型.class,例如:String.class
对象.getClass(),例如:”hello”.getClass()
Class.forName(),例如:Class.forName(“java.lang.String”)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值