深入理解JVM-类加载器深入解析(2)

加载:就是把二进制形式的java类型读入java虚拟机中

连接:

  • 验证:

  • 准备:为类变量分配内存,设置默认值.但是在到达初始化之前,类变量都没有初始化为真正的初始值

  • 解析:解析过程就是在类型的变量的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程

初始化:为类变量赋予正确地初始值

类实例化:

为新的对象分配内存

为实例变量赋默认值

为实例变量赋正确地初始值

java编译器为它编译的每一个类都至少生成一个实例初始化方法,在java的class文件中,这个实例初始化方法被称为"".针对源代码中每一个类的构造方法,java编译器都产生一个方法

类的加载

类的加载的最终产品是位于内存中的Class对象

class对象封装了类在方法区内的数据结构,并且向java程序员提供了访问方法区内的数据结构的接口

有两种类型的类加载器

  1. java虚拟机自带的加载器
  • 根类加载器(Bootstrap)

  • 扩展类加载器(Extension)

  • 系统(应用)类加载器(System)

  1. 用户自定义的类加载器
  • java.lang.ClassLoader的子类

  • 用户可以定制类的加载方式

类加载器并不需要等到某个类被"首次主动使用"时再加载它

JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)

如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

类的验证

类被加载后,就进入了连接阶段.连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去.

类的验证的内存

  • 类文件的结构检查

  • 语义检查

  • 字节码验证

  • 二进制兼容性的验证

类的准备

在准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值.例如对于以下Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0.


public class Sample{ private static int a = 1;
public static long b;
static {
b=2;
}
}
类的初始化

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值.在程序中,静态变量的初始化有两种途径;

  1. 静态变量的生命处进行初始化;

  2. 在静态代码块中进行初始化.

例如在以下代码中,静态变量a和b都被显示初始化,而静态变量c没有被显示初始化,它将保持默认值0


public class Sample{ private static int a=1; //在静态变量的声明处进行初始化
public static long b;
public static long c;
static{
b=2; // 在静态代码块中进行初始化
} }

静态变量的声明语句,以及静态代码块都被看做类的初始化语句,java虚拟机会按照初始化语句在类文件中的先后顺序来一次执行它们.例如当一下Sample类被初始化后,它的静态变量a的取值为4.


public class Sample{ static int a= 1;
static {a=2;}
static {a=4;}
public static void main(String args[]){
System.out.println("a="+a)//打印a=4 } }

类的初始化步骤:

  • 加入这个类还没有被加载和连接,那就先进行加载和连接

  • 加入类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类

  • 加入类中存在初始化语句,那就一次执行这些初始化语句

当java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口.

  • 在初始化一个类时,并不会先初始化它所实现的接口.

  • 在初始化一个接口时,并不会先初始化它的父接口


/** * 当一个接口在初始化时,并不要求其父接口都完成了初始化 * 只有在真正使用到父接口的时候(如引用接口中所定义的常量时),才会初始化 * * 在加上-XX:TraceClassLoading这个参数以后我们可以得知虚拟机的加载情况 * 如果是MyChild5是Class类,并且b变量是public static的时候可以看得到 * MyParent5和MyChild5都被加载了;如果b变量改为了public static final类型的 * 则可以看到MyParent5和MyChild5都没有被加载 * * 如果MyChild5是接口的话,则b变量默认是public static final类型的产量,是在 * 所以在编译阶段就会存入调用这个常量所在的方法的类的常量池中,所以并不会去加载MyParent5和MyChild5字节码文件 * * 初始化: * 我们在MyParent5中加入一个静态变量,如果MyParent5接口被初始化,一定会打印出MyParent5 invoked * 但是实际上并不会打印出,如果把MyParent5 改成了class类的话,则会打印出, * 说明在初始化一个类时,并不会先初始化它所实现的接口 * * 我们再定义MyParent5_1和MyGrandpa5_1,在直接调用MyParent5_1的时候我们可以知道MyParent5_1被初始化了, * 但是并没有打印出MyGrandpa5_1 invoked,说明接口的父类并不会被初始化 */ public class MyTest5 { public static void main(String[] args) {
//System.out.println(MyChild5.b);
System.out.println(MyParent5_1.THREAD);
}
} interface MyGrandpa5{ public static Thread THREAD = new Thread(){
{
System.out.println("MyGrandpa5 invoked");
}
};
} interface MyParent5 {
//class MyParent5 {
public static int a = 5;
//public static int a = new Random().nextInt(5);
public static Thread THREAD = new Thread(){
{
System.out.println("MyParent5 invoked");
}
};
} class MyChild5 implements MyParent5 { public static int b = 6;
//public static int b = new Random().nextInt(4); } interface MyGrandpa5_1{ public static Thread THREAD = new Thread(){
{
System.out.println("MyGrandpa5_1 invoked");
}
};
} interface MyParent5_1 { public static int a = 5;
//public static int a = new Random().nextInt(5);
public static Thread THREAD = new Thread(){ {
System.out.println("MyParent5_1 invoked");
}
}; public static Thread THREAD2 = new Thread(){
{
System.out.println("MyParent5_1 invoked2");
}
};
}

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化.只有当程序首次适用特定接口的静态变量时,才会导致该接口的初始化.

只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用

调用ClaossLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化

类加载器

类加载器用来把类加载到java虚拟机中.从JDK1.2版本开始,类的加载过程才用父类委托机制,这种机制能更好地保证java平台的安全.在此委托机制中,除了java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器.当java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类

在父亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器

java虚拟机自带了以下几种加载器:

  • 根类加载器(Bootstrap):该加载器没有付加载器.它负责加载虚拟机的核心类库,如java.lang.*等.根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库.根类加再起的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类

  • 扩展类加载器(Extension):它的父加载器为根类加载器.它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre\lib\ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载.扩展类加载器是纯java类,是java.lang.ClassLoader类的子类

  • 系统类加载器(System):也称为应用类加载器,它的附加在其为扩展类加载器.它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器.系统类加载器是纯java类,是java.lang.ClassLoader类的子类

除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器.java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类

从表象上来看这些类加载器是一种继承关系,实际上应该是一种包含关系

class Parent2 {
static int a =3;
static {
System.out.println("parent2 static block");
}
} class Child2 extends Parent2{
static int b = 4;
static {
System.out.println("Child2 static block");
}
} public class MyTest10 { static {
System.out.println("Mytest10 static block");
} public static void main(String[] args) {
/**
* Mytest10 static block
* ----
* parent2 static block
* ----
* 3
* ----
* Child2 static block
* 4
*/
Parent2 parent2;
System.out.println("----");
parent2 = new Parent2();
System.out.println("----");
System.out.println(parent2.a); System.out.println("----");
System.out.println(Child2.b); }
}
class Parent3 {
static int a = 3; static {
System.out.println("Parent3 static block");
} static void doSomething() {
System.out.println("do something");
}
} class Child3 extends Parent3 {
static {
System.out.println("Child3 static block");
}
}
public class MyTest11 { public static void main(String[] args) {
/**
* Parent3 static block
* 3
* ----
* do something
* 调用Child3.a定义在父类中,所以是对父类的一个主动使用,
* 谁拥有这个静态变量就是对谁的一个主动使用
* 如果是用子类去调用父类的静态变量也好,静态方法也好,都是对父类的主动使用
*
*/
System.out.println(Child3.a);
System.out.println("----");
Child3.doSomething();
}
}
class CL{
static {
System.out.println("Class CL");
}
}
//调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
public class MyTest12 {
public static void main(String[] args) throws Exception {
/**
* class jvm.classloader.CL
* ----
* Class CL
* class jvm.classloader.CL
*
*/
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> aClass = loader.loadClass("jvm.classloader.CL");
System.out.println(aClass); System.out.println("----");
aClass = Class.forName("jvm.classloader.CL");
System.out.println(aClass);
}
}

java学习笔记# #jvm#

深入理解JVM-类加载器深入解析(2)的更多相关文章

  1. JVM 类加载器深入解析以及重要特性剖析

    1.类加载流程图 从磁盘加载到销毁的完整过程. 2.类加载流程图2 1.加载: 就是把二进制形式的java类型读入java虚拟机中 2.连接: 验证.准备.解析. 连接就是将已经读入到内存的类的二进制 ...

  2. 深入理解Java类加载器(一):Java类加载原理解析

    摘要: 每个开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这个异常背后涉及到的是Java技术体系中的类加载机制.本文简述了JVM三种预定义类加载器,即 ...

  3. 深入理解Java类加载器(ClassLoader)

    深入理解Java类加载器(ClassLoader) Java学习记录--委派模型与类加载器 关于Java类加载双亲委派机制的思考(附一道面试题) 真正理解线程上下文类加载器(多案例分析) [jvm解析 ...

  4. 深入理解Java类加载器(ClassLoader) (转)

    转自: http://blog.csdn.net/javazejian/article/details/73413292 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Ja ...

  5. 深入理解JVM虚拟机6:深入理解JVM类加载机制

    深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体 ...

  6. 深入理解Java类加载器(二):线程上下文类加载器

    摘要: 博文<深入理解Java类加载器(一):Java类加载原理解析>提到的类加载器的双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器的实现方式.在Java ...

  7. JVM类加载器的分类

    类加载器的分类 JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader). 从概念上来讲,自定 ...

  8. 深入JVM类加载器机制,值得你收藏

    先来一道题,试试水平 public static void main(String[] args) { ClassLoader c1 = ClassloaderStudy.class.getClass ...

  9. 深入理解Java类加载器(1):Java类加载原理解析

    1 基本信息 每个开发人员对Java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这背后就涉及到了java技术体系中的类加载.Java的类加载机制是技术体系中比较核心的 ...

  10. JVM 类加载器命名空间深度解析与实例分析

    一.创建Sample 1.创建实例 public class MyPerson { private MyPerson myPerson; public void setMyPerson(Object ...

随机推荐

  1. 表格树控件QtTreePropertyBrowser编译成动态库(设计师插件)

    目录 一.回顾 二.动态库编译 1.命令行编译动态库和测试程序 2.vs工具编译动态库和测试程序 3.安装文档 4.测试文档 三.设计师插件编译 1.重写QDesignerCustomWidgetIn ...

  2. code forces 1173 C. Nauuo and Cards

    本文链接:https://www.cnblogs.com/blowhail/p/10990833.html Nauuo and Cards 原题链接:http://codeforces.com/con ...

  3. 如何确保TCP协议传输稳定可靠?

    TCP,控制传输协议,它充分实现了数据传输时的各种控制功能:针对发送端发出的数据包确认应答信号ACK:针对数据包丢失或者出现定时器超时的重发机制:针对数据包到达接收端主机顺序乱掉的顺序控制:针对高效传 ...

  4. 你确定你会写 Dockerfile 吗?

    如今 GitHub 仓库中已经包含了成千上万的 Dockerfile,但并不是所有的 Dockerfile 都是高效的.本文将从五个方面来介绍 Dockerfile 的最佳实践,以此来帮助大家编写更优 ...

  5. Facebook也炒币吗?Libra币是什么?

    Facebook 在上周发布了加密数字货币,称为 Libra币. 太火爆了,很多人都在关注和讨论,包括一些科技大佬们都很积极的讨论(当然,这里指的是真正的科技大佬,比如 马化腾.王兴等,而不是指哪些割 ...

  6. 源码阅读 - java.util.concurrent (一)

    java.util.concurrent这个包大致可以分为五个部分: Aomic数据类型 这部分都被放在java.util.concurrent.atomic这个包里面,实现了原子化操作的数据类型,包 ...

  7. [Vue 牛刀小试]:第十六章 - 针对传统后端开发人员的前端项目框架搭建

    一.前言 在之前学习 Vue 基础知识点的文章中,我们还是采用传统的方式,通过在 html 页面上引用 vue.js 这个文件,从而将 Vue 引入到我们的项目开发中.伴随着 Node.js 的出现, ...

  8. c++学习书籍推荐《C++沉思录》下载

    百度云及其他网盘下载地址:点我 编辑推荐 经典C++图书,应广大读者的强烈要求再版 目录 第0章 序幕第一篇 动机第1章 为什么我用C++第2章 为什么用C++工作第3章 生活在现实世界中 第二篇 类 ...

  9. 搭建oj平台

    欢迎使用https://github.com/QingdaoU/OnlineJudgeDeploy

  10. UVA514 铁轨 Rails:题解

    题目链接:https://www.luogu.org/problemnew/show/UVA514 分析: 入站序列是1-n,入站后判断如果等于出站序列的当前值,则直接出站.否则就在栈里待着不动.模拟 ...