新增:《创建一个自身类的静态对象变量,究竟会如何执行?》

This commit is contained in:
2022-07-24 15:03:05 +08:00
parent efe9316303
commit 6c42ef519f

View File

@@ -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 变量没有指定初始值,那默认值就是 0count2 指定了初始值是 3 Singleton 类的唯一对象(实例)要创建会调用构造方法,构造方法里又对 count1 和 count2 进行了自增 1 的运算,那结果自然就是 count1 是 1count2 是 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 为 1count2 为 1
4: invokespecial #6 // Method "<init>":()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 赋值给 count2count2 此时为 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 自增 1count1 此时为 1count2 此时为 4
8: invokespecial #6 // Method "<init>":()V
// 2.4 对象创建成功将对象引用赋值给静态对象变量 singleton
11: putstatic #4 // Field singleTon:Lorg/example/Singleton;
// 3、结束方法
14: return
}
```
很显然了count2 最后是被自增为 4 了。