JVM——深入理解类加载器
类加载器相关内容有很多,大概分以下几个关键点进行学习,
参考链接 https://www.bilibili.com/video/av47756459
目录
概览
JVM与程序的生命周期
类加载器生命周期
加载
连接
初始化
主动使用与被动使用
常量
数组成员变量
反编译
助记符
类加载器准备阶段与初始化阶段
接口的初始化
类实例化
类加载器
JVM自带的类加载器
自定义的类加载器
双(父)亲委托机制
命名空间
类的卸载
概览
类型(类,接口,枚举)的加载(从硬盘加载到内存),连接(字节码的校验(字节码可以被人为修改)、类之间关系的确定),初始化(静态变量的赋值)均是在程序运行期间完成的
JVM与程序的生命周期
以下几种情况JVM结束生命周期:
(1)执行System.exit()方法
(2)程序正常执行结束
(3)程序运行过程中遇到错误或异常而异常终止
(4)由于操作系统错误引起JVM进程终止
类加载器生命周期
加载
将类的.class 文件中的二进制数据读取到内存中,将其放入运行时数据区的方法区(1.8后有所改动)内,在内存中创建一个java.lang.Class 对象(规范并未明确说明Class对象放置区域,hotspot将其放置在方法区,不同于其他对象放置在堆中),用来封装类在方法区内的数据结构。
- 类的加载的最终产品是位于内存中的class对象
- class对象封装了类在方法区中的数据结构,并向java程序提供了访问方法区内数据结构的接口
加载.class 文件的方式:
— 从本地系统加载
— 通过网络下载
— 从zip等归档文件中加载
— 从专有数据库中加载
— 从java源文件动态编译为.class 文件(动态代理)
连接
(1)验证:确保加载类的字节码的正确性(2)准备:为类的静态变量分配内存,并将其初始化为默认值(3)解析:把类中的符号引用(间接引用)变为直接引用(使用指针指向内存位置)
初始化
为类的静态变量赋予正确的初始值(将静态变量的声明语句和静态代码块看做类的初始化语句)
使用:
卸载:从内存中销毁(OSGI)
主动使用与被动使用
在类加载,连接,初始化过程中,java 程序对类的使用分为两种:
(1)自动使用(7种)
— 创建类的实例(不是声明)
— 访问某个类或接口的静态变量或给静态变量赋值(子类调用父类的静态变量不会引起子类的主动使用)
— 调用类的静态方法(子类调用父类的静态方法不会引起子类的主动使用)
— 反射
— 初始化类的子类
— JVM启动时被标为启动类(包含main方法)的类(Java Test)
— 从JDK1.7开始的动态语言支持
(2)被动使用
除了主动使用中的7种方法,其余使用java类的方法都称为被动使用,均不会导致类的初始化(不影响加载,连接)。
所有java虚拟机实现必须在每个类或接口被java程序“首次主动使用”才初始化。
package test;public class MyTest1 {public static void main(String[] args) {System.out.println(Child.string);}
}class Parent {static String string = "hello";static {System.out.println("Parent static block");}
}class Child extends Parent {static {System.out.println("Child static block");}
}
由于没有主动使用Child类,Child类没有初始化,但是进行了加载!
-XX:+TraceClassLoading 用于追踪类的加载信息并打印,添加至idea配置信息的VM options,即JVM参数(关于VM options,参考:https://blog.csdn.net/upgroup/article/details/81052047)。
常量
对于final属性的成员变量(常量),编译期常量会存入到调用此常量的方法所在类的常量池中。本质上,调用类并没有直接引用到定义常量的类。因此,并不会触发定义常量的类的初始化。即使删除定义常量的类的class文件也不影响使用,编译过后就完全不需要定义常量的类了。
若是 final 属性的非编译期常量(如 随机数)即 运行期常量,则不同于上述情况。
数组成员变量
建立对象数组,不会主动使用对象类。对于数组实例来说,类型是JVM运行时动态生成的,表示为[***,动态生成的类型其父类型就是Object。
反编译
将已编译的编程语言还原到未编译的状态。java中,就是将.class文件转换成.java文件。使用命令 javap 。(编译使用javac)
助记符
ldc, 表示将int,float 或 string 常量值从常量池推送至栈顶。
bipush表示将单字节常量(-128 ~ 127)推送至栈顶。
sipush 表示短整型常量推送至栈顶。
iconst_1 (m1 ~ 5)表示将int型常量(m1 ~ 5)推送至栈顶。
anewarray 创建一个引用类型的数组,并将其引用值压入栈顶。
newarray 创建一个指定原始类型的数组,并将其引用值压入栈顶。
类加载器准备阶段与初始化阶段
public class TestClass2 {public static void main(String[] args) {Test test = Test.getTest();System.out.println(test.a);System.out.println(test.b);}
}class Test {static int a;static Test test = new Test();Test() {a++;b++;System.out.println(a);System.out.println(b);}static int b = 0;static Test getTest() {return test;}
}
此例中,类加载器先在准备阶段顺序执行静态代码块,将成员变量赋默认值(a,b为0,test为null),然后再执行类的初始化阶段,a不改变;test执行new Test() ,将a,b值改为1;b初始化值改为0。
简单理解,第一次执行等号左边,第二次执行等号右边。
接口的初始化
由于接口的成员默认都是final的常量,会放置调用此常量的方法所在类的常量池中
不同于类,初始化接口,不会引起父接口的初始化 (不影响加载)。在初始化一个类时,也不会初始化它实现的接口。但,仍然需要加载。只有当程序首次使用特定接口的静态变量时才会初始化该接口。
public class TestInterface {public static void main(String[] args) {System.out.println(ChildInterface.b);}
}interface ParentInterface {int a = 1 / 0;
}interface ChildInterface extends ParentInterface {int b = new Random().nextInt(10) + 1;
}
这段代码不会报错,即,不会引起ParentInterface 的初始化。若是将接口改为类则会报错。
(能在编译时初始化的,就不延迟到运行时解决)
类实例化
- 为新的对象分配内存
- 为实例变量赋默认值
- 为实例变量赋正确的初始值
- java编译器为它编译的每一个类都至少生成一个实例初始化方法,在class文件中,这个初始化方法称为<init>方法,针对源代码中的每一个构造方法,java编译器都生成一个<init>方法。
类加载器
JVM自带的类加载器
- 跟类加载器(Bootstrap) (听着这么熟悉?一个前端开发框架)
- 扩展类加载器(Extension)
- 系统(应用)类加载器(System)
自定义的类加载器
- java.lang.ClassLoader 的子类
- 定制类加载方式
类加载器并不需要该类“首次使用”才加载该类。JVM规范允许类加载器在预料某个类将被使用时,预先加载。若加载出现错误或缺失class文件,将在该类“首次主动使用”时才报告错误。若一直未使用,则不会报错。
双(父)亲委托机制
在双亲委托机制中,各个类加载器按照父子关系形成树形结构。除了启动类加载器(根类加载器)之外,所有类加载器有且只有一个父加载器。
每一个类加载器通过特定的目录进行加载。
首先不自己加载,而是通过自己的父亲加载,父亲又通过父亲的父亲进行加载直到启动类加载器。再自顶向下进行加载,看看到底谁能够加载。
由父加载器加载的类不能访问由子加载器所加载的类。
命名空间
每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。
不同的命名空间中类的完整名字可以相同,但是,相同命名空间中不能出现两个相同的完整类名。
因此当实例化多个自己定义的类加载器时,不同的类加载器都会进行加载类。
当在一个类中创建引用对象时,对象所对应的类由加载包含该对象的类的加载器进行加载(同时遵循双亲委托机制)。
子加载器所加载的类能够访问父加载器所加载的类。但是,由父加载器加载的类不能访问由子加载器所加载的类。
类的卸载
当MySample类被加载、连接、初始化后,其生命周期就开始了。当代表类Class对象不再被引用,不可触及时,Class对象就会终结其生命周期,MySample类在方法区内的数据就会被卸载,从而结束其生命周期。
一个类何时结束其生命周期,取决于代表它的Class对象何时结束其生命周期。
由Java虚拟机自带的类加载器(根类、扩展、系统类加载器)所加载的类,在虚拟机的生命周期中始终不会被卸载。JVM会始终使用这些类加载器,这些类加载器会始终使用它们所加载的类的Class对象,这些Class对象始终是可以触及的。
由用户自定义的类加载器所加载的类可以被卸载。
JVM——深入理解类加载器相关推荐
- 【深入理解JVM】:类加载器与双亲委派模型
转载自 [深入理解JVM]:类加载器与双亲委派模型 类加载器 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段" ...
- Java虚拟机JVM学习05 类加载器的父委托机制
Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap ...
- JVM有哪些类加载器?
站在Java虚拟机的角度来看,只存在两种不同的类加载器: 1.启动类加载器(Bootstrap ClassLoader),使用C++语言实现,是虚拟机自身的一部分: 2.其他所有的类加载器,由Java ...
- JVM系列之类加载器
前言 上节我们介绍了类加载的时机和过程,对类加载有了个初步的认识,上节我们有不断提到一个东西:类加载器,那么什么是类加载器?又有哪些类加载器?类加载器之间的联系等等一些问题,今天我们将会围绕这些点展开 ...
- 【JVM】Java类加载器设计原理(ClassLoader源码解读/ SPI机制/ 绕开双亲委派/ 常见Java虚拟机)
目录 1. 什么是类加载器 2. 类加载器加载的过程 3. Class文件读取来源 4. 类加载器的分类 5. 那些操作会初始化类加载器 6. 类加载器的双亲委派机制 6.1 双亲委派机制机制的好处 ...
- JVM之Java类加载器
前言 通过对Java类加载机制的了解,可以知道大概流程和各自的功能.其中类加载部分的功能是把类的Class文件读入内存,并创建java.lang.Class对象.这部分功能是由类加载器完成的. 1.类 ...
- JVM 自定义的类加载器的实现和使用
1.用户自定义的类加载器: 要创建用户自己的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,该方法根据参数指定类的名 ...
- 欧尼酱讲JVM(03)——用户自定义类加载器
用户自定义类加载器 在Java的日常应用程序开发中,类的加载几乎是由"引导类加载器,扩展类加载器,系统类加载器"这三种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来 ...
- 深入理解JVM(1):类加载器
文章目录 一.类加载简介 1.简介 2.Java虚拟机与程序的生命周期 3.类的加载.连接与初始化(类加载的最重要的3个阶段) 3.1加载 3.2连接 3.3 初始化 4.类的使用和卸载(类加载的剩余 ...
最新文章
- 一种精确从文本中提取URL的思路及实现
- 为什么要打jar_生活在西北的兰州人过春节为什么要打太平鼓?
- 关于项目过程能力基线的几个讨论
- ehcache常用API整理
- SpannableStringUtil实现丰富文字效果
- Oracle 11g R2的卸载与重装
- 数据挖掘:围绕 统计与概率、分类与聚类、检索方法 ,原理演示或应用程序
- python 线性拟合 图_python线性拟合
- 滴滴这车值不值得上?前Google全球技术总监郄小虎说来来来
- cubemx spi 中断_STM32 SPI在使用中断时丢弃数据
- VC线程同步技术剖析
- 年前的面试经历(二)
- Oracle RAC系列之:ASM基本操作维护
- Windows7 下载android源码
- Matlab画图常用的指令是啥,matlab画图常用命令
- Python基础(Day 2)(数值 字符串 布尔 列表)
- IAR For MSP430编译后菜单栏丢失解决方法
- 微信朋友圈点赞测试用例
- windows系统加了一个别的系统!------centos_6 by VMware
- 尚医通 (十八)微信登录