前言:

        最近在看一本《深入理解java虚拟机》的书,在学类加载机制和结合众多大佬的表达并结合自己的理解写下这一篇博客,记录自己的困惑,以防以后忘记。

 

阅读须知:

        此次博客以启发性代码和解释进行学习。在阅读时,按照代码和提示进行思考分析为什么,请思考过后在看答案来验证自己的思考。(此博客为个人观点且本人水平有限,如有错,请批评指正

  

小题测试:(先思考,并写出自己的答案后在往下看

package com.wen.demo.test.MyClass;

class Singlen{
    private static Singlen singlen= new Singlen();
    static int count1;
    static int count2=0;


    public Singlen(){
        count1++;
        count2++;
    }

    public static Singlen getInstance(){
        return Singlen.singlen;
    }
}
public class MyClass {

    public static void main(String[] args) {

        Singlen singlen= Singlen.getInstance();
        System.out.println("count1的值为:"+Singlen.count1);
        System.out.println("count2的值为:"+Singlen.count2);
    }
}

                                              对于以上代码,请先思考,输出的是什么。然后才往下面看。

                                                                                       。

                                                                                       。

                                                                                       。

                                                                                       。

概念:

         我们在学java时都有接触过,运行java程序之前,会将我们写的*.java文件编译成.class文件。但是好像我们在学的时候,老师或许就讲了这个,然后就没然后了。

 

          我们先看看这个张图:

 

 

          基本上是分为这7份阶段,此次只讲讲前5个阶段。其中验证——准备——解析通常称为一个连接阶段

 

加载阶段:

         在此阶段,虚拟机需要完成三件事情:

         (1)通过一个类的全限定名来获取定义此类的二进制字节流

         (2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

         (3)在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

                                                          (以上来自《深入理解java虚拟机》书上内容)

          上面的解释已经很清楚了,如果你还是不能理解,先看接下来这个 简化版:

          在加载的时候,虚拟机查找我们写的.java文件,编译成字节码后加载到内存里,然后在内存中生成一个java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

           嗯,好像还是有点长,在简单点就是查找并加载类的二进制数据到内存中。

 

验证阶段:

          验证是连接阶段的第一步,他主要是确保Class文件的字节流中的信息符合虚拟机的要求,并且不会危害虚拟机的自身的安全。为什么要验证,不都是我们写的编译来的吗?假设虚拟机不验证字节流文件,那样放一些恶意恶意的字节流,那感觉会“灰常”开心些吧!~

 

准备阶段:

            这一阶段,主要是为类的静态变量分配内存,并将其初始化为默认值。例如:

           public static int value =123;

            上面这个静态变量,在准备阶段:

                         将value值初始化为0而不是123

                         将value值初始化为0而不是123

                         将value值初始化为0而不是123

             当然,这是通常情况下,想想女生每个月总有那么几天,所以我们java虚拟机也要有那么几天的特殊情况嘛,。怎么特殊呢?

         public static final int value =123;

            加一个final关键字后,这个value就是个常量了。常量在编译阶段就会存入调用类的常量池中,所以在这个准备阶段时,java虚拟机就会将value赋值了。

解析阶段:

          解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程(这里我也不是特别懂,太多东西了,看的脑阔疼)想要深入了解请去阅读《深入理解java虚拟机》第7章220页

 

初始化阶段:

        这一步是类加载过程中的最后一步了,前面的类加载中除了用户应用程序可用通过自定义类加载器参与外,其他都是有JVM主导和控制的,到这一个阶段才是真正懂得执行类中定义的java程序代码。

       比如:我们在准备阶段的时候,已经为变量初始化了一次(此初始化不是现在这个初始化)这时,虚拟机要为类的静态变量赋予的初始化值(就是我们写的代码定义的值value=123啦

                      注:此次不涉及类的初始化,那样介绍太多了。持续懒惰中~!

                                                                                       。

                                                                                       。

                                                                                       。

                                                                                       。

      看完上面的,在想想自己的之前写的答案,下面我们将公布答案:

                                                    

      Why?怀疑人生了?还是高兴我作对了。如果做错的同学,请再看一次5个阶段理解一下。

      流程简单解析:  

           我们在调用时单例时new 实例属于主动使用(首次使用才有效,具体下一篇博客介绍)。这时类开始初始化,类的加载顺序是从上往下的。初始化了Singlen类,count1和count2都已经加了1是吧~!类的加载顺序是从上往下的,这时count1是1,count2也是1。接下来走count1,再走count2了。

             由于count1没有被赋值,可以理解为我们程序猿给他赋了值:

    static int count1  = 1;

             count2被我们自己赋值过了是不是?那么虚拟机在会将0赋值给count2。这时count2就是0了。所以结果就是1和0咯

 

 

总结:

         没想到,这篇博文我会写了快3小时了。但是,也是收获满满滴。java虚拟机部分是基础,对于我们来说是必须去学的,只有懂了,我们才会学会去优化,才能走的更远。谢谢大家,注大家学习进步,工作愉快~!

 

程序人生,与君共勉~!

 

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐