JAVA 类生命周期

  如上图所示,Java类的生命周期如图所示,分别为加载、验证、准备、解析、初始化、使用、卸载。其中验证、准备、解析这三个步骤统称为链接。

  加载:JVM根据全限定名来获取一段二进制字节流,将二进制流转化为方法区的运行时数据结构,在内存中生成一个代表该类的Java.lang.Class对象,作为方法区这个类的各种数据访问入口。

  验证:验证是链接的第一步,主要验证内容分为文件格式验证、元数据验证、字节码验证、符号引用验证。

  准备:准备阶段是为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。

  解析:解析阶段是虚拟机将常量池的符号引用替换为直接引用的过程。符号引用所引用的目标不一定在内存中,而转换为直接引用之后,引用直接指向到对应的内存地址。

  初始化:初始化会执行<Clinit>()方法,会对static属性进行赋值,对于static final修饰的基本类型和String类型则在更早的javac编译的时候已经加载到常量池中了。

  经过初始化之后,Java类已经加载完毕。

  

  JVM并没有强制规定什么时候进行类的加载,但是对于初始化规定了有且5种情况必须被初始化:

  1. 遇到new、getstatic、putstatic、invokestatic这四个字节码的执行时,如果类还没有被初始化,则必须被初始化。new为创建对象,剩下三个为操作静态变量。
  2. 使用java.lang.reflect对类进行反射操作的时候,如果该类还没有被加载,则加载该类。
  3. 如果对一个类进行初始化的时候,要先对其父类先进行初始化。
  4. 当虚拟机启动的时候,需要一个Main方法入口,虚拟机会先初始化这个类。
  5. 当使用JDK1.7动态语言支持的时候,如果一个java.lang.invoke.MethodHandle实例最终解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,如果对应的类没有初始化、则会被初始化。

  常在笔试题中遇到的就是类记载相关知识,如下面代码,先不看答案想想会打印出什么

例子1:

 1 public class SuperClass {
 2     public static int value = 123;
 3
 4     static {
 5         System.out.println("super class init");
 6     }
 7 }
 8
 9 public class SubClass extends SuperClass {
10
11     static {
12         System.out.println("Sub class init");
13     }
14 }
15
16 public class ClassInit {
17     public static void main(String[] args) {
18         System.out.println(SubClass.value);
19     }
20 }

   打印结果如下:

  

  解析:当Main方法执行的时候,不会对SubClass类进行初始化,因为调用静态变量时只会初始化直接定义该变量的类,因此,上述代码只有SuperClass会被初始化。而SubClass并不会被初始化。

  我们稍微修改一个上述代码,将main方法放入子类中执行,执行main方法之后,代码会怎么执行呢?

例子2:

 1 public class SuperClass {
 2     public static int value = 123;
 3
 4     static {
 5         System.out.println("super class init");
 6     }
 7 }
 8
 9 public class SubClass extends SuperClass {
10
11     static {
12         System.out.println("Sub class init");
13     }
14
15     public static void main(String[] args) {
16         System.out.println(SubClass.value);
17     }
18 }

  打印如下图:

  

  解析:根据上述类初始化规定。根据第四条执行main方法时候必须初始当前类,因此触发了SubClass的初始化。根据第三条,如果要触发SubClass,必须先对SuperClass进行初始化。因此会先进行SuperClass的初始化、执行完成后执行SubClass初始化,最后等SubClass初始化完毕,打印出Main方法的中的语句。

例子3:

 1 public class StaticTest {
 2
 3     static int b = 200;
 4
 5     static StaticTest st = new StaticTest();
 6
 7     static {
 8         System.out.println("1");
 9     }
10
11     {
12         System.out.println("2");
13     }
14
15     StaticTest() {
16         System.out.println("3");
17         System.out.println("a=" + a + ",b=" + b+",c="+c+",d="+d);
18     }
19
20     public static void staticFunction() {
21         System.out.println("4");
22     }
23
24     int a = 100;
25
26     static int c = 300;
27
28     static final int d=400;
29
30     public static void main(String[] args) {
31         staticFunction();
32     }
33 }

   执行结果如下:

  

  分析:代码执行之后的结果跟我一开始预想的不大一样,我们按照执行顺序进行分析。当我们执行Main方法的之前,javac需要先将代码编译,在这个时候d属性已经完成了赋值。前面说过,在执行main方法之前,会对main方法所在的类进行初始化。根据属性是否静态,我们大概可以将代码分为两部分:

  1、静态代码

 1     static int b = 200;
 2
 3     static StaticTest st = new StaticTest();
 4
 5     static {
 6         System.out.println("1");
 7     }
 8     public static void staticFunction() {
 9         System.out.println("4");
10     }

  2、非静态代码:

 1     {
 2         System.out.println("2");
 3     }
 4
 5     StaticTest() {
 6         System.out.println("3");
 7         System.out.println("a=" + a + ",b=" + b + ",c=" + c + ",d=" + d);
 8     }
 9
10     int a = 100;

  把代码分成两部分,主要是为了区分哪些是类初始化里的代码(<clinit>()中的代码,在类初始化的时候执行),哪些对象初始化代码(<init>()中的代码,对象初始化的时候执行)。main方法触发了类的初始化,因此会执行<clinit>()中的代码,执行顺序从上而下,先完成b=200赋值语句,紧接着执行 static StaticTest st = new StaticTest(),而对st的赋值则触发了对象初始化方法,因此会执行<init>()方法,即非静态代码,对象的初始化执行顺序和类初始化执行顺序不相同,类初始化执行顺序  属性初始化 =》代码块 =》方法初始化。因此在非静态代码中执行顺序为: 第10行=》第2行=》第6行=》第7行。所以最早打印出2、3。紧接着打印a、b、c、d数值的时候a、b、d已经完成赋值。完成对象初始化之后,继续执行上面的静态代码,打印出1。等类已经完成了加载,执行main方法,打印出4。

  

双亲委派模型

  在java类加载器对Class进行加载的时候,如果两个类被不同的类加载器加载,则这两个类不相等。通过equals(),instanceof等方法判断结果为false。关于Java的类加载器,大概可以划分为以下几种:

  • 启动类加载器(Bootstrap ClassLoader):Bootstrap ClassLoader是唯一一个通过JVM内部的类加载器,通过C++实现。负责加载<JAVA_HOME>/lib中,或者被-Xbootclasspath参数所指定的路径中的,或者被虚拟机识别的类库记载到虚拟机内存中,仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib中也不会被加载。启动类加载器无法被Java程序直接使用,如果需要把加载器请求委派给引导类加载,则直接使用null代替即可。
  • 扩展类加载器(Extension ClassLoder):这个加载器负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库。
  • 应用程序类加载器(Application ClassLoader):应用程序类加载器,负责加载用户路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自义定类加载,则该类加载就是默认的类加载器。

我们的应用程序大部分是通过上面三种类加载器配合完成的,如果有特殊需求,还可以自定义自己的类加载器。包括类记载器在内,各种类加载器的关系可以这样表示。

  上述关系图称为双亲委派模型,除了Bootstrap ClassLoad加载器,其他的类加载器都有自己的父类。当一个类加载器获取到一个类加载任务时,先将该类丢给父加载器加载, 如果父加载器不能加载则自己加载。这种加载方式就称之为双亲委派。如上图所示,自定义类加载器获取到一个加载任务,一层层往上丢,所以最先让启动类加载器加载,如果启动类加载器能加载,则启动类加载器加载,启动类加载器不能加载,则丢给扩展类加载器,如果扩展类加载器不能加载,则丢给应用类加载器,如果应用类加载器不能加载,才丢给自定义加载器加载。

  上述的加载方式看起来特别麻烦,但是却解决了一个很重要的问题。比如自定义类加载器获取到一个Java.lang.Object的任务,则让Bootstrap ClassLoader加载,否则如果用户自己定义了一个Java.lang.Object会跟rt.jar中的类产生冲突,通过双亲委派模型,则用户自己写的Object将永远不会被加载到。

   双亲委派模型是Java虚拟机推荐给开发者的类加载实现,并不是一个强制性约束。在一些情况下双亲委派模型是会被破坏的,比如为了加载JNDI提供者的代码,设计出来的线程上下文加载器。又比如OSGI环境下规则也不大一样。

转载于:https://www.cnblogs.com/null-qige/p/10059345.html

JAVA 类加载机制学习笔记相关推荐

  1. Java锁机制学习笔记——synchronized 和 Lock

    为什么80%的码农都做不了架构师?>>>    synchronized synchronized关键字相信大家都不陌生了,作为java关键字,它可以帮助我们实现对方法的加锁同步.它 ...

  2. 两道面试题,带你解析Java类加载机制

    2019独角兽企业重金招聘Python工程师标准>>> 在许多Java面试中,我们经常会看到关于Java类加载机制的考察,例如下面这道题: class Grandpa {static ...

  3. java/android 设计模式学习笔记(1)--- 单例模式

    前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...

  4. 谈谈 Java 类加载机制

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 来源:Rainstorm , github.com/c-rainstorm/blog/blob/m ...

  5. Java类加载机制的理解

    算上大学,尽管接触Java已经有4年时间并对基本的API算得上熟练应用,但是依旧觉得自己对于Java的特性依然是一知半解.要成为优秀的Java开发人员,需要深入了解Java平台的工作方式,其中类加载机 ...

  6. 《疯狂Java讲义》学习笔记 第六章 面向对象(下)

    <疯狂Java讲义>学习笔记 第六章 面向对象(下) 6.1包装类 基本数据类型 包装类 byte Byte short Short int Integer long Long char ...

  7. Java基础篇 学习笔记

    List item Java基础篇 学习笔记 java基础篇 第1章 计算机.程序和java概述 学习笔记 1.1什么是计算机 简单来说:计算机就是 ' 存储 ' 和 ' 处理 ' 数据的电子设备. ...

  8. 01.Java 编程入门学习笔记20210307

    Java 编程入门学习笔记-day01 第0章:编程入门 1.计算机的概述 计算机 = 硬件 + 软件 1.1硬件:冯诺依曼体系 CPU: CPU的衡量标准:速度的计量单位是赫兹(Hz),1Hz相当于 ...

  9. Java微服务学习笔记(一):微服务架构的概念理解

    Java微服务学习笔记 Tips:入门学习时粗略整理,仅供参考 (一):架构的基础理解 文章目录 Java微服务学习笔记 前言 一.微服务是什么? 二.常用开源微服务框架演化 1. Dubbo 2. ...

最新文章

  1. Eclipse插件开发中File和IFile的转换
  2. 开发日记-20190428
  3. 前端学习(1292):文件写入操作
  4. python根据行名称生成二维数组
  5. javascript中的原型
  6. 树莓派智能小车python论文_基于树莓派循迹小车设计概述
  7. 分布式事务控制解决方案
  8. 摩拜服务器维护,摩拜的后台是云服务器
  9. hibernate之自定义持久化实现
  10. Groovy模板引擎
  11. linux下,如何迁移mysql数据库存放目录。利用软连接简单快捷实现移动到home数据盘下...
  12. 直线段的矢栅转换算法(DDA算法、中心画线算法、Bresenham算法)
  13. CxImage 使用报错解决办法
  14. BIOS不识别硬盘,DIY解决希捷固件门(图解)
  15. 在线报表设计实战系列 – ②制作表格类报表
  16. 专访李明远,理清直达号九大疑问
  17. (13)web安全|渗透测试|网络安全 注入/跨库查询/文件读写/常见的防护原理 详细图解
  18. BigDecimal源码分析。
  19. 【Grasshopper基础8】电池的序列化与反序列化 Serilization of Grasshopper Component
  20. 条形码识别Matlab版本

热门文章

  1. C#中具有进程间通信的Singleton应用程序
  2. Django 3.0 正式发布,开始支持异步功能
  3. GitHub 被爆开始实名制,以便于执行美国贸易制裁
  4. python 提取列表元素_Python如何获取列表中元素的索引,python,获得,某个,index
  5. hashset去重原理_9道Java集合面试题,搞定了再去投简历吧!
  6. xhell 镜像_2020官网下载Xshell 6.0.189.0中文版
  7. mysql long类型_BAT架构师46面试题:spring+多线程+Redis+MySQL(建议收藏)
  8. java 注释添加引用_java – 如何引用注释处理中的方法的实现?
  9. java对象名不可以是_java运行一个方法时如何得到该个对象的名字(不是类的名字)....
  10. 查看linux进程的设备io,Linux下查看进程IO工具iopp