深入理解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. javascript匿名函数自调用

    // 匿名函数的自调用 /*var f1 = function() { console.log('我是一个匿名函数!'); }*/ // f1(); // 上面是定义一个匿名函数,然后调用,其实上面就 ...

  2. Spring Boot2从入门到实战:统一异常处理

    都说管理的精髓就是“制度管人,流程管事”.而所谓流程,就是对一些日常工作环节.方式方法.次序等进行标准化.规范化.且不论精不精髓,在技术团队中,对一些通用场景,统一规范是必要的,只有步调一致,才能高效 ...

  3. Spring Boot 整合 Freemarker,50 多行配置是怎么省略掉的?

    Spring Boot2 系列教程接近完工,最近进入修修补补阶段.Freemarker 整合貌似还没和大家聊过,因此今天把这个补充上. 已经完工的 Spring Boot2 教程,大家可以参考这里: ...

  4. python 基本数据类型之列表

    #列表是可变类型,可以增删改查#字符串不可变类型,不能修改,只能生成新的值. #1.追加 # user_list = ['李泉','刘一','刘康','豆豆','小龙'] # user_list.ap ...

  5. vSphere克隆虚机重启网卡报错

    使用VMware vSphere克隆虚机,修改IP重启网卡报错: 解决报错: 修改 /etc/udev/rules.d/70-persistent-net.rules 文件,克隆后会多出eth2和et ...

  6. 最牛MongoDB灾难恢复(WiredTiger.wt文件损坏,Mongo无法启动)

    WiredTiger.wt文件是mongoDB的元数据文件,存储了其他数据库表的元数据信息.笔者最近遇到了WiredTiger.wt文件损坏的情况,MongoDB无法启动,数据库中的重要数据危在旦夕. ...

  7. ElasticStack学习(五):ElasticSearch索引与分词

    一.正排索引与倒排索引 1.什么是正排索引呢? 以一本书为例,一般在书的开始都会有书的目录,目录里面列举了一本书有哪些章节,大概有哪些内容,以及所对应的页码数.这样,我们在查找一些内容时,就可以通过目 ...

  8. zookeeper的客户端应用

    什么zookeeper? ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供 ...

  9. asp.net core系列 68 Filter管道过滤器

    一.概述 本篇详细了解一下asp.net core filters,filter叫"筛选器"也叫"过滤器",是请求处理管道中的特定阶段之前或之后运行代码.fil ...

  10. Java 新特性总结——简单实用

    lambda表达式 简介 lambda 表达式的语法 变量作用域 函数式接口 内置函数式接口 默认方法 Stream(流) 创建 stream Filter(过滤) Sorted(排序) Map(映射 ...