JVM学习(一):Java类的加载机制

目录

目录

一、类加载机制

1、类加载?

1.1 什么是类加载机制?

首先,在代码被编译器编译后生成的二进制字节流(.class)文件;
然后,JVM把Class文件加载到内存,并进行验证、准备、解析、初始化;
最后,能够形成被JVM直接使用的Java类型的过程。
--这就是类加载机制

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

1.2 加载.class 文件的方式
  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip、jar等文件中加载
  • 从专有数据库中提取.class文件
  • 将Java远文件动态编译为.class文件

2、类加载流程图

类加载机制

二、类加载机制阶段详解

1、类的加载

类的加载是类加载机制过程的第一个阶段,该阶段主要完成三件任务:

  • ①. 通过类的全限定名来获取类的二进制字节流。
  • ②. 将字节流中所有代表的静态存储结构转化为【方法区】的运行时数据结构。
  • ③. 在内存Java堆中生成一个代表这个类的Java.lang.Class对象,作为方法区中这个类的各种数据的访问入口。

2、连接

在经历类的加载过程后,生成了类的java.lang.Class对象,接着会进入连接阶段。连接阶段负责将类的二进制数据合并如JRE(Java运行时环境)中。类的连接大致分为三个阶段。

2.1 验证阶段

验证:确保被加载的类符合JVM规范和安全。
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:

  • 文件格式验证:
    验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
  • 元数据验证:
    对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证:确保解析动作能正确执行。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

2.2 准备(重点!!)

准备阶段:为类的静态变量(static filed)在【方法区】分配内存,并附上默认初始值(0或者null值)。

  • 静态变量在方法去分配内存
  • 静态变量在分配内存后,附上初始值。

静态常量(static final filed)会在准备阶段直接将程序设定的值附上。
例如:

static final int a = 10; 
// 该静态常量a 会在【准备阶段】直接将10赋值。
static int b = 11;
// 该静态变量b 在【准备阶段】只会赋值初始值0,等到了【初始化】阶段会将真正的11赋值给静态变量b。

2.3 解析

解析:把类中的符号引用转换为直接引用。

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

  • 符号引用,就是一组符号来描述目标,可以是任何字面量。
  • 直接引用,就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

3、初始化(重点!!)

初始化,为类的静态变量赋予正确的初始值。
初始化阶段是执行类构造器<clinit>()方法。

3.1 在Java中堆类变量惊喜初始值设定有两种方式:
  • ①声明类静态变量是制定初始值。
  • ②使用静态代码块为类静态变量制定初始值。
3.2 JVM初始化步骤
  • ① 如果这个类还没有被加载和连接,则程序先加载并连接该类。(其实就是执行上面的类加载、连接两步骤)
  • ② 如果的直接父类还没有被初始化,则先初始化其直接父类。
  • ③ 如果这个类中有初始化语句,则系统会一次执行这些初始化语句。
3.3 类初始化时机

类初始化时机,有且只有主动引用时才会触发类的初始化。
被动引用则不会触发类初始化。
类初始化时间后面单独详细说明。

4、使用

类的正常使用。

5、卸载

类的卸载需要根据【该类对象不再被引用+GC回收 】来判断何时被卸载。

  • ①由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。因为一直被引用着。
  • ②由用户自定义的类加载器加载的类是可以被卸载。

三、类的加载时机与初始化时机

(一)类的加载时机

当应用程序启动的时候,所有的类都会被一次性加载吗?
答案是否定的。不能,因为如果一次性加载,内存资源有限,可能会影响应用程序的正常运行。

类是什么时候i被加载的呢?
当一个类真正被加载的时机是在创建对象的时候,才会执行类加载。
例如:A a= new A();该类的加载,只有在创建对象的时候才加载类。
其中,最先加载拥有main方法的主线程所在的类。

(二)类的初始化时机(重要!!)

引用方式主要分为两种:主动引用被动引用
有且只有主动引用才会触发类初始化的过程。被动引用不会触发类初始化过程。

主动引用

有且只有主动引用才会触发类初始化的过程。触发主动引用的方式有以下五种:

    1. 创建类的实例。即通过new的方式,new一个对象。
      例如:
    A a = new A();
    
    1. 调用类的静态变量(非final修饰的常量) 和静态方法。
      代码示例:
    • Test3.class代码如下
    public class Test3 {
        public static final int A = 10;// 静态常量, 不会触发初始化,该代码在连接准备阶段已赋值。
        public static int B = 11;// 静态变量, 在被外界调用时,会主动触发类的初始化。
    
        static{//静态代码块,如果类初始化,则一定会执行该代码块
            System.out.println("test3..print~~~");
        }
    
        public void fun() {//实例方法,只有实例化后代码才能调用此方法
            System.out.println("test3..fun~~~");
        }
    
        public static void fun2() {//静态方法,在被外界调用时,会主动触发类的初始化。
            System.out.println("test3..static fun2...");
        }
    }
    
    • mian()方法测试代码:
    public class TestStatic {
        public static void main(String[] ags) {
    //        申明类时,不会主动触发初始化流程.
            Test3 test3;
    
    //        类的静态常量,不会触发初始化,该代码在连接准备阶段已赋值。
            System.out.println("test3 A "+Test3.A);
    
    //        类的静态变量,在被外界调用时,会主动触发类的初始化。【注意,此时会初始化Test3】
            System.out.println("test3 B " + Test3.B);
    
    //      直接调用test3的静态方法fun(),也会主动触发类的初始化工作。
            test3.fun();
        }
    }
    
    • 输出内容:
    test3 A 10
    test3..print~~~
    test3 B 11
    
    错误:这里会发现,static静态代码块先执行,然后再执行static变量初始化。(@油炸小居崽 的指正)
    正确:static静态代码块不会比static变量初始化优先执行,可以采用静态代码块中输出静态变量验证。
    
    1. 通过反射对类进行调用。
      例如:Class.forName("com.jx.Test2");
    • Test3.class示例代码:
      public class Test3 {
          public static final int A = 10;// 静态常量, 不会触发初始化,该代码在连接准备阶段已赋值。
          public static int B = 11;// 静态变量, 在被外界调用时,会主动触发类的初始化。
      
          static{//静态代码块,如果类初始化,则一定会执行该代码块
              System.out.println("test3..print~~~");
          }
      
          public void fun() {//实例方法,只有实例化后代码才能调用此方法
              System.out.println("test3..fun~~~");
          }
      
          public static void fun2() {//静态方法,在被外界调用时,会主动触发类的初始化。
              System.out.println("test3..static fun2...");
          }
      }
      
      • mian()方法类,测试代码:
      public class TestStatic {  
          public static void main(String[] ags) {
              try {
                  Class<?> aClass = Class.forName("com.jx.Test3");//调用此代码,则类会初始化。
                  System.out.println("aClass " + aClass);
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }
          }
      }
      
      • 输出内容:
      test1..print~~~
      aClass class com.jx.Test1
      
    1. 初始化某个类的子类,则父类也会被初始化。
    • Test4.class示例代码:
        public class Test4 extends Test3 { // 继承了Test3
            public void fun4() {
                System.out.println("test4...fun4...");
            }
        }
    
    • 测试代码:
        public class TestStatic {
            public static void main(String[] ags) {
                Test4 test4 = new Test4();
                test4.fun4();
            }
        }
    
    • 输出内容:
    test3..print~~~
    test4...fun4...
    
    1. Java虚拟机启动时,指定的main方法所在的类,需要被提前初始化。
    • 测试代码:
        public class TestStatic {
            static {
                System.out.println("test static ...");
            }
            public static void main(String[] ags) {
                Test4 test4 = new Test4();
                test4.fun4();
            }
        }
    
    • 输出内容:
    test static ...
    test3..print~~~
    test4...fun4...
    
被动引用

被动引用,不会发生类的初始化过程。
被动引用又分为三种方式:

    1. 当访问一个类的静态变量时(该静态变量是父类所持有),只有真正声明这个变量的类才会初始化。
      子类调用父类的静态变量,只有父类初始化,而子类不会进行初始化。
    • 代码示例:
      public class SuperClass { // 父类
          public static int A = 7; // 父类静态变量
      
          static { // 静态代码块在初始化时执行
              System.out.println("super class static ...");
          }
      }
      
      public class SubClass extends SuperClass { // 子类继承父类
          static { // 静态代码块在初始化时执行
              System.out.println("sub class static ...");
          }
      }
      
      public class TestStatic2 {
          public static void main(String[] args) {
              System.out.println("A =" + SubClass.A);  // 调用子类继承的父类静态变量
          }
      }
      
    • 输出内容:
      super class static ...
      A =7
      
    1. 通过数据定义引用类,不会触发类的初始化。
      因为是数据进行new,而对应的应用类没有被new,所以该类没有触发任何主动引用。
    • 代码示例
      public class TestStatic3 {
          public static void main(String[] args) {
              SuperClass[] superClasses = new SuperClass[3];
              System.out.println(superClasses);
          }
      }
      
    • 输出内容:
    [Lcom.jx.SuperClass;@193b845
    
    1. final 常量不会触发类的初始化,因为编译阶段就存储在常量池中。
      //常量类
      public class ConstClass {
          static{
              System.out.println("常量类初始化!");
          }
          
          public static final String HELLOWORLD = "hello world!";
      }
       
      //主类、测试类
      public class NotInit {
          public static void main(String[] args){
              System.out.println(ConstClass.HELLOWORLD);
          }
      }
      

四、类生命周期与JVM生命周期

(一) 类的生命周期

当一个类被加载、连接、初始化后,它的生命周期就开始了。
当这个类的class对象不再被引用,即类不可触及时,Class对象就会结束生命周期。这个类在方法区的数据也会被卸载,从而结束这个类的生命周期。
所以,一个类结束生命周期,取决于代表它的Class对象何时结束生命周期。

(二)JVM生命周期

Java虚拟机结束生命周期的情况:

  • 1.执行了System.exit()方法.
    1. 程序正常执行结束。
    1. 程序在执行过程中遇到了异常或错误而并未处理,导致异常终止。
    1. 由于依赖的操作系统出现错误,而导致Java虚拟机进程终止。

参考

http://www.ityouknow.com/jvm/2017/08/19/class-loading-principle.html
https://blog.csdn.net/xorxos/article/details/80490240
https://www.cnblogs.com/qiuyong/p/6407418.html?utm_source=itdadao&utm_medium=referral

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,573评论 18 399
  • 1、什么是类的加载机制 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据...
    huststl阅读 380评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 文/大龙 我爱 太阳和星辰, 也爱大地上一粒微尘 我爱浩瀚,也爱卑谦 我爱 大海和蓝天 也爱无名的小花开在水边 我...
    大龙_0415阅读 180评论 2 5
  • 这部电影是我在3月份被一位up主种草的,于是乎在空暇时候把它找出来… 故事发生在美丽的阿尔卑斯山脉,天真无邪的...
    破晓Apple阅读 656评论 0 1