Skip to content

Commit

Permalink
Add JetBrains Activation code!
Browse files Browse the repository at this point in the history
  • Loading branch information
ChenSino committed Mar 22, 2022
1 parent 3b644e2 commit 3967d22
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 10 deletions.
6 changes: 4 additions & 2 deletions .vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ module.exports = {
"/docs/java/jvm/": [
"",
"SetObjectNull",
"StringAdd"
"StringAdd",
"NewObject"
],
//java进阶
"/docs/java/advance/": [
Expand Down Expand Up @@ -161,7 +162,8 @@ module.exports = {
"/docs/linux/": [
"",
"CommonUsedCMD",
"Manjaro"
"Manjaro",
"AutoDetectFileChange"
],
"/docs/tools/": [
"",
Expand Down
137 changes: 137 additions & 0 deletions docs/java/jvm/NewObject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
title: 对象创建过程
date: 2022/3/22
author: chenkun
sidebar: 'auto'
publish: true
keys:
categories:
---


### 1、在类中本地变量引用自身类,会引发的问题

```java
public class BaseFormBean {
private BaseFormBean baseBean = new BaseFormBean();
{
String bar = "非静态代码块中字段";
}

public BaseFormBean() {
String foo = "构造方法中的字段";
System.out.println("构造方法被调用.");
}

public static void main(String[] args) {
new BaseFormBean();
}
}
```



此问题用于研究对象初始化的过程,以上程序运行结果如下:

```shell
Exception in thread "main" java.lang.StackOverflowError
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:4)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:4)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:4)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:4)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:4)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:4)
```

代码抛出的是栈溢出,和我第一想法不太一样,一般想的是程序会不停的new 自身对象,最终会导致OOM堆溢出,认为是这样说明对对象初始化原理掌握的还不够。要想搞明白此问题,需要反汇编查看jvm的指令到底是按照什么顺序执行的,采用```javap -c BaseFormBean```反汇编,结果如下:

```shell
# javap -c BaseFormBean.class
Compiled from "BaseFormBean.java"
public class com.chen.bean.BaseFormBean {
public com.chen.bean.BaseFormBean();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class com/chen/bean/BaseFormBean
8: dup
9: invokespecial #3 // Method "<init>":()V
12: putfield #4 // Field baseBean:Lcom/chen/bean/BaseFormBean;
15: ldc #5 // String 非静态代码块中字段
17: astore_1
18: ldc #6 // String 构造方法中的字段
20: astore_1
21: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
24: ldc #8 // String 构造方法被调用.
26: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: return

public static void main(java.lang.String[]);
Code:
0: new #2 // class com/chen/bean/BaseFormBean
3: dup
4: invokespecial #3 // Method "<init>":()V
7: pop
8: return
}
```

构造方法的汇编代码解析(需要对照JVM指令集手册查看指令代表的含义)

> 1. aload_0:将第一个引用类型本地变量推送至栈顶,此处指的其实是this,就是当前对象自身的地址
> 2. invokespecial :调用构造方法,此处调用的是父类Object构造方法
> 3. new:创建一个自身对象, 并将其引用引用值压入栈顶
> 4. dup: 复制栈顶数值并将复制值压入栈顶
> 5. invokespecial这一指令后边注释Method "<init>":()V,此方法是真正的给对象进行赋值,在此之前只是给对象初始化一个空间,里面都是空值,此方法初始化对象,因此需要给baseBean字段进行赋值,赋值时会创建一个新的BaseFormBean对象,就要调用new BaseFormBean(),调用此方法后就会进入一个死循环,不停调用自身的构造方法,因此会抛出栈溢出。后续指令不会执行……,因此误会打印东西出来
> 6. putfield:如果上一步不抛出异常,正常的话是要执行putfield指令,把上一步创建的对象赋值给baseBean,
**注意:**

上面的非静态代码块也不会执行,原因是其放的位置是在```private BaseFormBean baseBean = new BaseFormBean();```后面,如果把它放到前面,它依然会执行的。

```java

public class BaseFormBean {

{
System.out.println("执行非静态代码块");
String bar = "非静态代码块中字段";
}
private BaseFormBean baseBean = new BaseFormBean();

public BaseFormBean() {
String foo = "构造方法中的字段";
System.out.println("构造方法被调用.");
}

public static void main(String[] args) {
new BaseFormBean();
}
}
```

执行结果:

```shell
执行非静态代码块
执行非静态代码块
执行非静态代码块
执行非静态代码块
执行非静态代码块
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:691)
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)
at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)
at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
at java.io.PrintStream.write(PrintStream.java:526)
at java.io.PrintStream.print(PrintStream.java:669)
at java.io.PrintStream.println(PrintStream.java:806)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:6)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:9)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:9)
at com.chen.bean.BaseFormBean.<init>(BaseFormBean.java:9)
```

15 changes: 7 additions & 8 deletions docs/java/jvm/StringAdd.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
title: 通过反汇编来看String的拼接
date: 2022-03-14 14:23:35
permalink: /pages/8a689b/
sticky: 2
categories:
- java
Expand Down Expand Up @@ -43,7 +42,7 @@ public class JvmTest {
}
```

先反汇编,到class文件所在目录打开控制台执行```javap -c 类名```
先反汇编,到class文件所在目录打开控制台执行`javap -c 类名`

```shell
$ javap -c JvmTest
Expand Down Expand Up @@ -74,17 +73,17 @@ public class com.chen.base.jvm.JvmTest {
}
```

接下来一个指令一个指令的分析,为什么```new String("hel") + "lo";```创建了两个对象。
接下来一个指令一个指令的分析,为什么`new String("hel") + "lo";`创建了两个对象。

| 行号 | 指令 | 含义 |
| 行号 | 指令 | 含义 |
| :--: | :-----------: | :----------------------------------------------------------: |
| 13 | new | 堆中创建一个StringBuilder对象,并把堆中此对象地址压入栈顶 |
| 14 | dup | 复制栈顶的值,压入栈顶 |
| 15 | invokespecial | 初始化StringBuilder,对象创建都是先分配内存,再初始化 |
| 16 | new | 堆中创建一个String对象,并把堆中此对象地址压入栈顶 |
| 17 | dup | 复制栈顶的值,压入栈顶 |
| 18 | ldc | 从常量池取出字符串"hel"并压入栈顶 |
| 19 | invokespecial | 和前面一样,先分配地址,再调用String的初始化方法,把"hel"压入new的对象中,注意看19行注释,初始化时有传参(Ljava/lang/String;),15行是"<init>":()V,无参的 |
| 19 | invokespecial | `和前面一样,先分配地址,再调用String的初始化方法,把"hel"压入new的对象中,注意看19行注释,初始化时有传参(Ljava/lang/String;),15行是"<init>":()V,无参的` |
| 20 | invokevirtual | 调用StringBuilder.append,并且也是有传参的,这一步是用之前创建并初始化过的StringBuilder空对象来和“hel”字符串先拼接 |
| 21 | ldc | 把字符串“lo”从常量池压入栈顶 |
| 22 | invokevirtual | 同上面一样,再用StringBuilder拼接“lo”字符串 |
Expand All @@ -94,7 +93,7 @@ public class com.chen.base.jvm.JvmTest {



小结:```new String("hel") + "lo";```该行代码在虚拟机执行时,是先创建一个StringBuilder并初始化(开始为空对象,注意空对象不是null),然后创建一个String对象并初始化(初始化后String对象内容是hel),然后调用用StringBuilder的append方法先把空对象和hel字符串拼接,然后再次调用append拼接lo.
小结:`new String("hel") + "lo";`该行代码在虚拟机执行时,是先创建一个StringBuilder并初始化(开始为空对象,注意空对象不是null),然后创建一个String对象并初始化(初始化后String对象内容是hel),然后调用用StringBuilder的append方法先把空对象和hel字符串拼接,然后再次调用append拼接lo.



Expand Down Expand Up @@ -129,12 +128,12 @@ System.out.println(s1 == s6);
```java
public class JvmTest {
public void test() {
String s2 = "hel" + "lo";
String s3 = "hel" + "lo";
}
}
```

反汇编,发现确实没有new新对象,是因为纯字符串相加在编译期间已经被优化了```String s2 = "hel" + "lo";```优化后就是```String s2 = "hello"```
反汇编,发现确实没有new新对象,是因为纯字符串相加在编译期间已经被优化了```String s3 = "hel" + "lo";```优化后就是```String s3 = "hello"```

```shell
$ javap -c JvmTest
Expand Down

0 comments on commit 3967d22

Please sign in to comment.