类的加载历程说庞大很庞大,说简朴也简朴,说庞大是由于细节许多,好比说今天要说的这个,可能许多人都不领会;说简朴,大致都知道类加载有这么几个阶段,loaded->linked->initialized,为了让人人能更轻松地知道我今天说的这个话题,我不详细说类加载的整个历程,改天有时间有精神了我将整个类加载的历程和人人好好说说(PS:我对类加载历程逐步清晰起来得益于当初在支付宝做cloudengine容器开发的时刻,那时引入了尺度的osgi,解决类加载的问题几乎是天天的屡见不鲜,信赖人人若是还在使用OSGI,那估量能体会我那时的那种痛,哈哈)。

本文我想说的是最后一个阶段,类的初始化,然则也不细说其中的历程,只围绕我们今天要说的睁开。

我们界说一个类的时刻,可能有静态变量,可能有静态代码块,这些逻辑编译之后会封装到一个叫做clinit的方式里,好比下面的代码:

class BadClass{
    private static int a=100;
    static{
        System.out.println("before init");
        int b=3/0;
        System.out.println("after init");
    }

    public static void doSomething(){
        System.out.println("do somthing");
    }
}

编译之后我们通过javap -verbose BadClass可以看到如下字节码:

{
  BadClass();
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void doSomething();
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String do somthing
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 10: 0
        line 11: 8

  static {};
    flags: ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: bipush        100
         2: putstatic     #5                  // Field a:I
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #6                  // String before init
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: iconst_3
        14: iconst_0
        15: idiv
        16: istore_0
        17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: ldc           #7                  // String after init
        22: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 2: 0
        line 4: 5
        line 5: 13
        line 6: 17
        line 7: 25
}

我们看到最后谁人方式static{},实在就是我上面说的clinit方式,我们看到静态字段的初始化和静态代码库都封装在这个方式里。

如果我们通过如下代码来测试上面的类:

public static void main(String args[]){
        try{
            BadClass.doSomething();
        }catch (Throwable e){
            e.printStackTrace();
        }

        BadClass.doSomething();
    }

人人以为输出会是什么?是会打印多次before init吗?实在不然,输出效果如下:

before init
java.lang.ExceptionInInitializerError
    at ObjectTest.main(ObjectTest.java:7)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.ArithmeticException: / by zero
    at BadClass.<clinit>(ObjectTest.java:25)
    ... 6 more
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class BadClass
    at ObjectTest.main(ObjectTest.java:12)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

也就是说实在是只输出了一次before init,这是为什么呢?

clinit方式在我们第一次自动使用这个类的时刻会触发执行,好比我们接见这个类的静态方式或者静态字段就会触发执行clinit,然则这个历程是不可逆的,也就是说当我们执行一遍之后再也不会执行了,若是在执行这个方式历程中泛起了异常没有被捕捉,那这个类将永远不可用,虽然我们上面执行BadClass.doSomething()的时刻catch住了异常,然则当代码跑到这里的时刻,在jvm里已经将这个类打上标记了,说这个类初始化失败了,下次再初始化的时刻就会直接返回并抛出类似的异常java.lang.NoClassDefFoundError: Could not initialize class BadClass,而不去再次执行初始化的逻辑,详细可以看下jvm里对类的状态界说:

enum ClassState {
    unparsable_by_gc = 0,               // object is not yet parsable by gc. Value of _init_state at object allocation.
    allocated,                          // allocated (but not yet linked)
    loaded,                             // loaded and inserted in class hierarchy (but not linked yet)
    linked,                             // successfully linked/verified (but not initialized yet)
    being_initialized,                  // currently running class initializer
    fully_initialized,                  // initialized (successfull final state)
    initialization_error                // error happened during initialization
  };

若是clinit执行失败了,抛了一个未被捕捉的异常,那将这个类的状态设置为initialization_error,而且无法再恢复,由于jvm会以为你这次初始化失败了,下次一定也是失败的,为了防止不停抛这种异常,以是做了一个缓存处置,不是每次都再去执行clinit,因此人人要特别注意,类的初始化历程可万万不能失足,失足就可能只能重启了哦。类的加载历程说庞大很庞大,说简朴也简朴,说庞大是由于细节许多,好比说今天要说的这个,可能许多人都不领会;说简朴,大致都知道类加载有这么几个阶段,loaded->linked->initialized,为了让人人能更轻松地知道我今天说的这个话题,我不详细说类加载的整个历程,改天有时间有精神了我将整个类加载的历程和人人好好说说(PS:我对类加载历程逐步清晰起来得益于当初在支付宝做cloudengine容器开发的时刻,那时引入了尺度的osgi,解决类加载的问题几乎是天天的屡见不鲜,信赖人人若是还在使用OSGI,那估量能体会我那时的那种痛,哈哈)。

本文我想说的是最后一个阶段,类的初始化,然则也不细说其中的历程,只围绕我们今天要说的睁开。

我们界说一个类的时刻,可能有静态变量,可能有静态代码块,这些逻辑编译之后会封装到一个叫做clinit的方式里,好比下面的代码:

class BadClass{
    private static int a=100;
    static{
        System.out.println("before init");
        int b=3/0;
        System.out.println("after init");
    }

    public static void doSomething(){
        System.out.println("do somthing");
    }
}

编译之后我们通过javap -verbose BadClass可以看到如下字节码:

{
  BadClass();
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void doSomething();
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String do somthing
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 10: 0
        line 11: 8

  static {};
    flags: ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: bipush        100
         2: putstatic     #5                  // Field a:I
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #6                  // String before init
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: iconst_3
        14: iconst_0
        15: idiv
        16: istore_0
        17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: ldc           #7                  // String after init
        22: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 2: 0
        line 4: 5
        line 5: 13
        line 6: 17
        line 7: 25
}

我们看到最后谁人方式static{},实在就是我上面说的clinit方式,我们看到静态字段的初始化和静态代码库都封装在这个方式里。

如果我们通过如下代码来测试上面的类:

public static void main(String args[]){
        try{
            BadClass.doSomething();
        }catch (Throwable e){
            e.printStackTrace();
        }

        BadClass.doSomething();
    }

人人以为输出会是什么?是会打印多次before init吗?实在不然,输出效果如下:

before init
java.lang.ExceptionInInitializerError
    at ObjectTest.main(ObjectTest.java:7)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.ArithmeticException: / by zero
    at BadClass.<clinit>(ObjectTest.java:25)
    ... 6 more
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class BadClass
    at ObjectTest.main(ObjectTest.java:12)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

也就是说实在是只输出了一次before init,这是为什么呢?

clinit方式在我们第一次自动使用这个类的时刻会触发执行,好比我们接见这个类的静态方式或者静态字段就会触发执行clinit,然则这个历程是不可逆的,也就是说当我们执行一遍之后再也不会执行了,若是在执行这个方式历程中泛起了异常没有被捕捉,那这个类将永远不可用,虽然我们上面执行BadClass.doSomething()的时刻catch住了异常,然则当代码跑到这里的时刻,在jvm里已经将这个类打上标记了,说这个类初始化失败了,下次再初始化的时刻就会直接返回并抛出类似的异常java.lang.NoClassDefFoundError: Could not initialize class BadClass,而不去再次执行初始化的逻辑,详细可以看下jvm里对类的状态界说:

enum ClassState {
    unparsable_by_gc = 0,               // object is not yet parsable by gc. Value of _init_state at object allocation.
    allocated,                          // allocated (but not yet linked)
    loaded,                             // loaded and inserted in class hierarchy (but not linked yet)
    linked,                             // successfully linked/verified (but not initialized yet)
    being_initialized,                  // currently running class initializer
    fully_initialized,                  // initialized (successfull final state)
    initialization_error                // error happened during initialization
  };

若是clinit执行失败了,抛了一个未被捕捉的异常,那将这个类的状态设置为initialization_error,而且无法再恢复,由于jvm会以为你这次初始化失败了,下次一定也是失败的,为了防止不停抛这种异常,以是做了一个缓存处置,不是每次都再去执行clinit,因此人人要特别注意,类的初始化历程可万万不能失足,失足就可能只能重启了哦。

推荐阅读

这么盛行的ZooKeeper,原来是这样设计的!

spring boot 引起的 “堆外内存泄露”

 

,

Sunbet 申博

Sunbet 申博www.bhjqxx.cn是Sunbet娱乐的官方网站。申博用20多年的时间,诠释了高品质、高效率、高信誉。开放的Sunbet、Sunbet等业务备受申博用户的追捧。

发布评论

分享到:

南昌人才招聘网:买车者的福音!凯迪拉克CT5部分地区跌至23.97万,CT4要尴尬了?
1 条回复
  1. AllbetGmaing
    AllbetGmaing
    (2020-06-11 02:00:30) 1#

    欧博注册网址www.dongfangculture.com欢迎进入欧博网址(Allbet Gaming),欧博网址开放会员注册、代理开户、电脑客户端下载、苹果安卓下载等业务。太好看了,看了一天

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。