zoukankan      html  css  js  c++  java
  • JAVA类型生命周期的开始阶段和使用阶段/以及创建对象的几种方式

       常量池用来保存字面量(字符串和final型变量)和符号引用(类全限定名,字段名字段描述符,方法名方法描述符)。

         某个类在常量池解析过程中报错,直到该类被真正使用才会抛出错误如类文件找不到,如果该类不被使用则不会抛出。

                                                                                                                                                               堆

        class文件1              线程二进制字节流                                                                                          Class实例1

        class文件2 ——————————————————→方法区——————————————→ Class实例2

        class文件n                 类加载器                                                                                                    Class实例n

           

    生命周期概述:一个类型的生命周期包括开始阶段的装载、连接和初始化,以及占java类型生命周期大部分时间的类型实例化、垃圾收集和对象终结,最后是java类型生命周期的结束,也就是从虚拟机中卸载类型。

           开始阶段:如上所说,java类型生命周期的开始阶段包括装载、连接、初始化。启动任何一个java程序时,虚拟机从加载一个类开始会自动执行装载、连接、初始化这三步,因此开始阶段的这三个过程也称为类加载机制。

                装载分为三步:1、虚拟机通过需要加载的类的全限定名来定位对应的class文件,并以线性二进制字节流形式将它们加载到虚拟机。2、虚拟机将这些字节流转化为方法区的内部数据结构(字节流代表的静态存储结构转化为方法区的运行时数据结构)。3、在堆内存中生成代表各个类型的java.lang.Class类型的实例对象(此实例对象是反射机制创建对象的基础)作为方法区那些类的各种数据的访问入口。由此可见方法区和堆在装载阶段就开始被分配和使用。一个虚拟机实例只有一个方法区和堆,被该虚拟机实例要运行的java程序的所有线程共享。

                

                实现装载的第一步动作的代码模块叫类加载器。java虚拟机有灵活的类加载器体系结构,从而使java应用程序得以用自定义的方式来实现类的装载。

                对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中唯一性,每一个类加载器都拥有一个独立的类命名空间。即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

                双亲委派模型:从虚拟机角度讲只有两种类加载器,由C++实现的启动类加载器,它是虚拟机的一部分,其他的类加载器都由java实现,独立于虚拟机外部,并且全继承自抽象类java.lang.ClassLoader。从开发人员角度讲大部分程序都会使用三种系统提供的类加载器:Bootstrap ClassLoader启动类加载器——负责加载%JAVA_HOME%lib目录中的类库或被-Xbootclasspath参数所指定的路径下的类库(文件名需要被虚拟机识别才能加载如.jar)。启动类加载器无法被程序直接引用,如果需要在程序中把加载请求委派给启动类加载器,直接使用null代替即可。Extension ClassLoader扩展类加载器负责加载%JAVA_HOME%jrelibext下的类库,或被java.ext.dirs系统变量所指定的类库,开发者可以直接使用扩展类加载器。System ClassLoaderApplication ClassLoader系统类加载器应用程序类加载器负责加载用户类路径环境变量CLASSPATH指定的类库。

                值得一提的是,装载的第一步在进行时,负责加载用户自定义类的类加载器AppClassLoader只会根据需要对用户自定义类进行按需加载,并不全部加载。

                连接亦分三步:1、验证——确保类文件的字节流包含信息符合当前虚拟机的规范。2、准备——在方法区内给类变量分配内存,并给类变量设置默认初始值。3、解析——将常量池内的符号引用(编译期间还未进行内存分配,只能用符号引用来代替实际引用)替换成直接引用。

                初始化:即给类变量赋予正确的初始值。(正确指的是程序员在程序中希望类变量具备的起始值,对比准备阶段的默认初始值而言)

                初始化包括两步:1)先初始化类的直接超类(第一个被初始化的类永远是Object)(接口初始化时不需要管父接口)

                                      2)执行本类的<clinit>方法(按代码顺序组织起来的类变量赋值语句和静态初始化块)

                只有在初始化阶段,虚拟机才开始执行字节码。

           使用阶段:

           类型实例化:虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,然后检查这个符号引用代表的类是否已被加载,连接(不要求类初始化)。如果已经加载,连接,不管类是否初始化,则进行实例化。如果没有则先执行类加载的三个自然过程(包括类初始化),等三阶段的最后阶段类初始化执行完之后再进行类实例化。类加载检查通过后将给新生对象分配内存,分配到空间后将空间初始化为0值(不包括对象头),这一步操作保证了实例字段在程序中可以不赋初始值就可以使用。接下来虚拟机对对象进行必要的设置如此对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息,这些信息被保存在对象头中。之后执行实例变量赋值语句或初始化块(按代码顺序执行)。最后执行<init>方法,把对象按程序员的意愿进行初始化。(对象的内存布局分为三部分:对象头,实例数据,对齐填充)从赋默认初始值0到执行构造方法,每创建一个对象都会执行一次。(类变量只初始化一次)

                  关于构造方法的执行:java编译器对某个类进行编译时,针对源代码中每个类的构造方法,编译器都产生一个<init>方法。如果类没有明确声明任何构造方法,编译器默认生成一个无参数构造方法,用于调用超类的无参数构造方法。(每个类至少有一个用来调用父类无参数构造方法的无参构造方法)。

                  子类实例化前一定先走一遍父类实例化的全过程(实例变量赋默认值,执行程序中实例变量赋值语句、执行初始化块,执行构造函数),父类实例化过程的最后一步执行父类的构造函数——取决于子类构造函数中super()的参数类型,如果没有super(),则执行父类的无参数构造函数。跟this()无关。

          

    public class NewTest {
      public int m=9;
      public static int i;
      public static NewTest d=new NewTest();

      {
        System.out.println("初始块执行"+"i="+i);
        System.out.println("m的值"+m);
      }

      public static int j=m();
      public static int mx(){
        System.out.println("实例变量赋值语句先执行");
        return 1;
      }
      public static int m(){
        System.out.println("类方法执行");
        return 1;
      }
      public NewTest(){
        System.out.println("构造函数执行");
      }
      public int x=mx();
      static{
        System.out.println("静态初始块执行");
      }
      public static void main(String[] str){
        NewTest ss=new NewTest();
      }
    }

    //1类加载连接 2类初始化 3类实例化
    //1类加载连接: i=0,d=null,j=0
    //2类初始化:d=new NewTest(),j=m(),static{System.out.println("父类静态初始块执行");}
    //(m=0,x=0|m=9,初始块执行i=0,m的值9,实例变量赋值语句先执行|父类构造函数执行。
    //类方法执行
    //父类静态初始块执行
    //3类实例化: 初始块执行i=0,m的值9,实例变量赋值语句先执行,父类构造函数执行。

    1.程序执行main()方法,即执行new NewTest();

    2.检查NewTest是否已被加载连接和初始化。

    3.如已连接:立即类实例化,1)给实例变量分配空间,赋默认初始值。2)按代码顺序执行实例变量赋值语句,初始化块。3)执行构造函数。

       如未连接则先进行类加载,连接(给类变量(包括引用变量)分配空间,赋默认初始值)。然后类初始化(按代码顺序执行类变量赋值语句,静态初始化块),最后类实例化。

       上面的例子中即是从类加载开始,具体的变化情况为:

         一:   类加载连接:给类变量赋默认初始值    i=0,d=null,j=0

         二:   类初始化:  给类变量赋程序设计的值或执行静态初始块     d=new NewTest(m=0,x=0(给实例变量分配空间及赋默认初始值);m=9,初始块执行i=0,m的值9,实例变量赋值语句先执行,x=1;构造函数执行),类方法执行,静态初始块执行

         三:   类实例化:  ss=new NewTest()  

       

            综上:

            类实例化时遵循四步:加载连接初始化父类,加载连接初始化子类。父类实例化全过程走一遍(除了没有创建引用指向父类),子类实例化。

            如果类初始化过程中需要进行类实例化,检查需要实例化的类是否已连接,如已连接:立即类实例化。

    执行顺序:(“/”表示具体由顺序决定)

    父类:静态变量/静态初始化块。

    子类:静态变量/静态初始化块。

    父类:变量/初始化块。

    父类:构造函数。

    子类:变量/初始化块。

    子类:构造函数。 

                                                                                    Java中创建对象的几种方式

    1. new最常见
    2. 反射用Class类实例对象的newInstance方法
    3. clone()
    4. 反序列化,用ObjectInputStream类实例的readObject()方法。

    反射创建对象:

    1.获取描述要创建的目标实例的Class类的实例

       Class  c=Class.forName("目标对象类名");             Class  c=目标对象类名.class;               Class  c=目标对象.getClass();(当然反射中用不到这种方式,反射中是去创造目标对象的,一开始没有目标对象)

    2.用class实例创建实例

       目标类  目标对象=c.newInstance();

    3.用class实例获取目标类的属性,方法

                   

               

           

    新生的小心情
  • 相关阅读:
    HTML表格的简单使用1
    守卫路由使用
    CSS高度塌陷问题解决方案
    CSS导航条nav简单样式
    Linux学习
    TYpeScript接口的使用
    javaScript中自定义sort中的比较函数,用于比较字符串长度,数值大小
    給eclipse添加字体,设置字体
    tomcat自动URLDecode解码问题(+号变空格)
    时间管理
  • 原文地址:https://www.cnblogs.com/jianmianruxin/p/6899641.html
Copyright © 2011-2022 走看看