From 6c42ef519f835c3c7b9b86262431a2a6e102d369 Mon Sep 17 00:00:00 2001 From: Charles7c Date: Sun, 24 Jul 2022 15:03:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E3=80=8A=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E4=B8=80=E4=B8=AA=E8=87=AA=E8=BA=AB=E7=B1=BB=E7=9A=84?= =?UTF-8?q?=E9=9D=99=E6=80=81=E5=AF=B9=E8=B1=A1=E5=8F=98=E9=87=8F=EF=BC=8C?= =?UTF-8?q?=E7=A9=B6=E7=AB=9F=E4=BC=9A=E5=A6=82=E4=BD=95=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=EF=BC=9F=E3=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...个自身类的静态对象变量,究竟会如何执行?.md | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 repos/issues/2022/03/24.创建一个自身类的静态对象变量,究竟会如何执行?.md diff --git a/repos/issues/2022/03/24.创建一个自身类的静态对象变量,究竟会如何执行?.md b/repos/issues/2022/03/24.创建一个自身类的静态对象变量,究竟会如何执行?.md new file mode 100644 index 000000000..04b24aa9f --- /dev/null +++ b/repos/issues/2022/03/24.创建一个自身类的静态对象变量,究竟会如何执行?.md @@ -0,0 +1,193 @@ +--- +title: 创建一个自身类的静态对象变量,究竟会如何执行? +author: 查尔斯 +date: 2022/03/24 21:30 +categories: + - Bug万象集 +tags: + - Java + - JVM +--- + +# 创建一个自身类的静态对象变量,究竟会如何执行? + +## 前言 + +**C:** 近两周在疯狂给项目组面试招聘,昨天晚上10点多,产品总监在面试群里发了一道题,问运行结果是什么,题目如下: + +```java {2-4} +class Singleton { + private static Singleton singleton = new Singleton(); + public static int count1; + public static int count2 = 3; + + private Singleton() { + count1++; + count2++; + } + + public static Singleton getInstance() { + return singleton; + } +} + +public class Test { + public static void main(String[] args) { + Singleton singleTon = Singleton.getInstance(); + System.out.println("count1=" + singleTon.count1); + System.out.println("count2=" + singleTon.count2); + } +} +``` + +这激起了我们几个干技术的热情,那就分析一下吧。 + +## 简单分析 + +1、简单看了下题目,这不是一个采用了饿汉式单例模式的单例类嘛,接下来当然是去找程序入口了。 + +2、在 Test 类的 main 方法中,首先调用了 Singleton 类的 getInstance() 方法,很显然这是要获取 Singleton 这个单例类的唯一对象(实例)了。 + +3、然后在获取到唯一对象(实例)之后,输出了 Singleton 类的两个静态成员变量 count1、count2 的值。(虽然通过对象名调用静态信息这种方式不推荐,但是对结果没有影响) + +4、看到这儿,两个类里也没别的地方有输出语句,所以最终运行结果就是要看看 count1、count2 的输出值了。 + +5、**重点来了:** 在调用 getInstance() 方法前,由于 Singleton 类没有加载,所以肯定要先加载类,由于 count1、count2、Singleton 的唯一对象(实例)都是静态的,所以它们会随着类的加载而加载。其中 int 类型的 count1 变量没有指定初始值,那默认值就是 0,count2 指定了初始值是 3, Singleton 类的唯一对象(实例)要创建会调用构造方法,构造方法里又对 count1 和 count2 进行了自增 1 的运算,那结果自然就是 count1 是 1,count2 是 4。 + +这么一顿火花带闪电的分析后,自信的将答案发到了群里。 + +``` +count1=1 +count2=4 +``` + +## 深度分析 + +很显然答错了,不然也不会单独记录了。之所以答错了,是因为忽略了静态信息的加载顺序,静态信息的加载顺序是由编码顺序决定的,上方分析中先入为主的把 count1 和 count2 加载完了,但实际上最先执行的是 Singleton 的唯一对象(实例)创建及变量赋值,随后才是执行 count1、count2。 + +我们可以通过 `javap -c Singleton.class` 反汇编一下字节码文件,反汇编后的 JVM 指令如下: + +```java +Compiled from "Test.java" +class org.example.Singleton { + public static int count1; + + public static int count2; + + public static org.example.Singleton getInstance(); + Code: + // 获取 singleton 静态对象变量,并将其值压入栈顶 + 0: getstatic #4 // Field singleton:Lorg/example/Singleton; + // 从当前方法返回 singleton 对象引用 + 3: areturn + + static {}; + Code: + // 1、创建 Singleton 类的对象,并赋值给静态对象变量 singleton + // 1.1 创建对象 + 0: new #5 // class org/example/Singleton + // 1.2 复制栈顶数值并将复制值压入栈顶 + 3: dup + // 1.3 调用 Singleton 类构造方法,count1 和 count2 自增 1,此时 count1 为 1,count2 为 1 + 4: invokespecial #6 // Method "":()V + // 1.4 对象创建成功将对象引用赋值给静态对象变量 singleton + 7: putstatic #4 // Field singleTon:Lorg/example/Singleton; + + // 2、将 3 赋值给 count2 + // 2.1 将 int 型 3 推送至栈顶 + 10: iconst_3 + // 2.2 为 count2 静态变量赋值 + 11: putstatic #3 // Field count2:I + + // 3、结束方法 + 14: return +} + +``` + +很显然了,count2 最后是被赋值为 3 了。 + +正确答案就是: + +``` +count1=1 +count2=3 +``` + +## 额外扩展 + +那如果真的想得到之前的结果呢? + +``` +count1=1 +count2=4 +``` + +只需要将 count1、count2 两个静态变量的顺序调整到 Singleton 类的唯一对象(实例)变量上方就可以了。 + +```java {2-4} +class Singleton { + public static int count1; + public static int count2 = 3; + private static Singleton singleton = new Singleton(); + + private Singleton() { + count1++; + count2++; + } + + public static Singleton getInstance() { + return singleton; + } +} + +public class Test { + public static void main(String[] args) { + Singleton singleTon = Singleton.getInstance(); + System.out.println("count1=" + singleTon.count1); + System.out.println("count2=" + singleTon.count2); + } +} +``` + +我们再次通过 `javap -c Singleton.class` 反汇编一下字节码文件,反汇编后的 JVM 指令如下: + +```java +Compiled from "Test.java" +class org.example.Singleton { + public static int count1; + + public static int count2; + + public static org.example.Singleton getInstance(); + Code: + // 获取 singleton 静态对象变量,并将其值压入栈顶 + 0: getstatic #4 // Field singleton:Lorg/example/Singleton; + // 从当前方法返回 singleton 对象引用 + 3: areturn + + static {}; + Code: + // 1、将 3 赋值给 count2,count2 此时为 3 + // 1.1 将 int 型 3 推送至栈顶 + 0: iconst_3 + // 1.2 为 count2 静态变量赋值 + 1: putstatic #3 // Field count2:I + + // 2、创建 Singleton 类的对象,并赋值给静态对象变量 singleton + // 2.1 创建对象 + 4: new #5 // class org/example/Singleton + // 2.2 复制栈顶数值并将复制值压入栈顶 + 7: dup + // 2.3 调用 Singleton 类构造方法,count1 和 count2 自增 1,count1 此时为 1,count2 此时为 4 + 8: invokespecial #6 // Method "":()V + // 2.4 对象创建成功将对象引用赋值给静态对象变量 singleton + 11: putstatic #4 // Field singleTon:Lorg/example/Singleton; + + // 3、结束方法 + 14: return +} + +``` + +很显然了,count2 最后是被自增为 4 了。 \ No newline at end of file