提交 e789512e 编写于 作者: 沉默王二's avatar 沉默王二 💬

图片本地化 JVM

上级 e5cffe69
......@@ -208,7 +208,7 @@
- [JVM到底是如何运行Java代码的?](docs/jvm/how-run-java-code.md)
- [我竟然不再抗拒Java的类加载机制了](docs/jvm/class-load.md)
- [详解Java的类文件(class文件)结构](docs/jvm/class-file-jiegou.md)
- [javap的角度轻松看懂字节码](docs/jvm/bytecode.md)
- [javap的角度轻松看懂字节码](docs/jvm/bytecode.md)
- [JVM字节码指令详解](docs/jvm/zijiema-zhiling.md)
- [虚拟机是如何执行字节码指令的?](docs/jvm/how-jvm-run-zijiema-zhiling.md)
- [HSDB(Hotspot Debugger)从入门到实战](docs/jvm/hsdb.md)
......
......@@ -18,7 +18,7 @@ We are all in the gutter, but some of us are looking at the stars. (我们都
ASM是一种通用Java字节码操作和分析框架。它可以用于修改现有的class文件或动态生成class文件。
> **ASM **is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to dynamically generate classes, directly in binary form. ASM provides some common bytecode transformations and analysis algorithms from which custom complex transformations and code analysis tools can be built. ASM offers similar functionality as other Java bytecode frameworks, but is focused on[performance](https://link.zhihu.com/?target=https%3A//asm.ow2.io/performance.html). Because it was designed and implemented to be as small and as fast as possible, it is well suited for use in dynamic systems (but can of course be used in a static way too, e.g. in compilers).
> **ASM **is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to dynamically generate classes, directly in binary form. ASM provides some common bytecode transformations and analysis algorithms from which custom complex transformations and code analysis tools can be built. ASM offers similar functionality as other Java bytecode frameworks, but is focused on[performance](https://asm.ow2.io/performance.html). Because it was designed and implemented to be as small and as fast as possible, it is well suited for use in dynamic systems (but can of course be used in a static way too, e.g. in compilers).
本篇文章分享的是对ASM的理解和应用,之前需要我们掌握**class字节码****JVM基于栈的设计模式,JVM指令**
......@@ -26,7 +26,7 @@ ASM是一种通用Java字节码操作和分析框架。它可以用于修改现
我们编写的java文件,会通过javac命令编译为class文件,JVM最终会执行该类型文件来运行程序。下图所示为class文件结构。
![](https://upload-images.jianshu.io/upload_images/1179389-17f68ac5dabe8d33.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/asm-43844b78-c01f-4990-b038-3c91ff2eeb34.jpg)
下面我们通过一个简单的实例来进行说明。下面是我们编写的一个简单的java文件,只是简单的函数调用.
......@@ -185,7 +185,7 @@ SourceFile: "Test.java"
JVM的指令集是基于栈而不是寄存器,基于栈可以具备很好的跨平台性。在线程中执行一个方法时,我们会创建一个栈帧入栈并执行,如果该方法又调用另一个方法时会再次创建新的栈帧然后入栈,方法返回之际,原栈帧会返回方法的执行结果给之前的栈帧,随后虚拟机将会丢弃此栈帧。
![](https://upload-images.jianshu.io/upload_images/1179389-9173e9d3cc8fd537.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/asm-e31b7e50-1d48-4eef-9552-6fa7e6c68fed.jpg)
### 局部变量表
......@@ -237,9 +237,9 @@ public int sub(int, int);
a = b + c 的字节码执行过程中操作数栈以及局部变量表的变化如下图所示
![](https://upload-images.jianshu.io/upload_images/1179389-c5ccfcca3e6fabaa.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/asm-4670450e-6199-4562-9cf4-354234c734c8.jpg)
![](https://upload-images.jianshu.io/upload_images/1179389-4d970cd306753276.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/asm-9808d639-327f-4796-80d4-1809be0b9106.jpg)
## ASM操作
......@@ -340,7 +340,7 @@ mv.visitEnd();
可以一键生成对应的ASM API代码
![](https://upload-images.jianshu.io/upload_images/1179389-d9960e4e54e3f547.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/asm-3c8c8db4-5b6a-4576-b147-62965d0e0c1c.jpg)
----
......
......@@ -6,7 +6,7 @@ tag:
- Java
---
# 轻松看懂字节码
# 从javap的角度轻松看懂字节码
### 01、字节码
......@@ -20,7 +20,7 @@ tag:
如今的 Java 虚拟机非常强大,不仅支持 Java 语言,还支持很多其他的编程语言,比如说 Groovy、Scala、Koltin 等等。
![](https://img-blog.csdnimg.cn/img_convert/c0f46bd591311e119d125e34c2ae4c5a.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/bytecode-dd31bbd6-c75c-4426-9437-c0f57ea3b86f.png)
来看一段代码吧。
......@@ -35,7 +35,7 @@ public class Main {
编译生成 Main.class 文件后,可以在命令行使用 `xxd Main.class` 打开 class 文件(我用的是 Intellij IDEA,在 macOS 环境下)。
![](https://img-blog.csdnimg.cn/img_convert/249e68ae6e099a96b4c9936e627305a2.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/bytecode-bd941085-ff0e-4abf-a5f9-afb0493bfed7.png)
......@@ -51,7 +51,7 @@ public class Main {
Java 内置了一个反编译命令 javap,可以通过 `javap -help` 了解 javap 的基本用法。
![](https://img-blog.csdnimg.cn/img_convert/1ac81d072e7a39e732f7aee755881640.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/bytecode-84b7af5c-93b1-4f63-bb30-946ab3d7e98c.png)
OK,我们输入命令 `javap -v -p Main.class` 来查看一下输出的内容。
......@@ -184,7 +184,7 @@ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
类访问标记,一共有 8 种。
![](https://img-blog.csdnimg.cn/img_convert/f6ad24641d314f5dd1b271aeeb41223d.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/bytecode-d12d6983-f427-40d2-bb4b-3a2c6c4c7806.png)
表明当前类是 `ACC_PUBLIC | ACC_SUPER`。位运算符 `|` 的意思是如果相对应位是 0,则结果为 0,否则为 1,所以 `0x0001 | 0x0020` 的结果是 `0x0021`(需要转成二进制进行运算)。
......@@ -332,7 +332,7 @@ Java 虚拟机是在加载字节码文件的时候才进行的动态链接,也
关于字段类型的描述符映射表如下图所示。
![](https://img-blog.csdnimg.cn/img_convert/f0833aa50e5df619c4f00bbcc0ae16b4.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/bytecode-cbf16ce9-7853-4050-a1c0-8b874f3b0c1e.png)
到此为止,第 2 个常量算是摸完了。组合起来的意思就是,声明了一个类型为 int 的字段 age。
......@@ -364,7 +364,7 @@ private int age;
字段的访问标志和类的访问标志非常类似。
![](https://img-blog.csdnimg.cn/img_convert/35d14839a3f4de81fbb676b035537435.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/bytecode-5f328e11-3486-4eb4-8fa9-5c5febfab894.png)
......@@ -373,7 +373,7 @@ private int age;
方法表用来描述接口或者类中声明的方法,包括类方法和成员方法,以及构造方法。方法的修饰符和字段略有不同,比如说 volatile 和 transient 不能用来修饰方法,再比如说方法的修饰符多了 synchronized、native、strictfp 和 abstract。
![](https://img-blog.csdnimg.cn/img_convert/64a12c57dcd8d3283aede4753c75fca3.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/bytecode-fd434d5c-ffc6-4a24-9787-98e573035068.png)
下面这部分为构造方法,返回类型为 void,访问标志为 public。
......@@ -450,4 +450,4 @@ private int age;
无论是从十六进制的字节码角度,还是 jclasslib 图形化查看反编译后的字节码的角度,也或者是今天这样从 javap 反编译后的角度,都能窥探出一些新的内容来!
初学者一开始接触字节码的时候会感觉比较头大,没关系,我当初也是这样,随着时间的推移,经验的积累,慢慢就好了,越往深处钻,就越能体会到那种“技术我有,雄霸天下”的感觉~
\ No newline at end of file
初学者一开始接触字节码的时候会感觉比较头大,没关系,我当初也是这样,随着时间的推移,经验的积累,慢慢就好了,越往深处钻,就越能体会到那种“技术我有,雄霸天下”的感觉~
......@@ -16,7 +16,7 @@ tag:
Java 在诞生的时候喊出了一个非常牛逼的口号:“Write Once, Run Anywhere”,为了达成这个目的,Sun 公司发布了许多可以在不同平台(Windows、Linux)上运行的 Java 虚拟机(JVM)——负责载入和执行 Java 编译后的字节码。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/class-load-7c6d4230-9154-4c7f-9ac1-122d39078442)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/class-load-01.png)
到底 Java 字节码是什么样子,我们借助一段简单的代码来看一看。
......@@ -167,7 +167,7 @@ sun.misc.Launcher$ExtClassLoader@15db9742
如果以上三种类加载器不能满足要求的话,程序员还可以自定义类加载器(继承 `java.lang.ClassLoader` 类),它们之间的层级关系如下图所示。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/class-load-fcef6ce9-5c54-4217-9681-8be69c456d8b)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/class-load-02.png)
这种层次关系被称作为**双亲委派模型**:如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。
......
---
category:
- Java核心
- JVM
tag:
- Java
---
# 自己编译JDK
很多小伙伴们做`Java`开发,天天写`Java`代码,肯定离不开`Java`基础环境:`JDK`,毕竟我们写好的`Java`代码也是跑在`JVM`虚拟机上。
一般来说,我们学`Java`之前,第一步就是安装`JDK`环境。这个简单啊,我们一般直接把`JDK`从官网下载下来,安装完成,配个环境变量就可以愉快地使用了。
......@@ -36,7 +46,7 @@
一图胜千言,各平台上的编译器支持如下表所示,按平台选择即可:
![](https://upload-images.jianshu.io/upload_images/1179389-8fe4ece6f45ccc74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-3b66d5b6-272f-47bd-88f7-47146a06ef06.png)
### **4、其他工具**
......@@ -75,7 +85,7 @@
`Mercurial`可以理解为和`Git`一样,是另外一种代码管理工具,安装好之后就有一个`hg`命令可用。
![](https://upload-images.jianshu.io/upload_images/1179389-b038992018f648bf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-cd8a19ba-e9f5-4a4a-a23c-17688f0f459d.png)
`OpenJDK`的源码已经提前托管到`http://hg.openjdk.java.net/`
......@@ -96,7 +106,7 @@
下载地址:`https://jdk.java.net/`
![](https://upload-images.jianshu.io/upload_images/1179389-7dc7742f5907f149.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-1bbbb1f8-da01-46e1-a793-487a25193c68.png)
选择你想要的版本下载即可。
......@@ -118,27 +128,27 @@
**配置JDK 8完成:**
![](https://upload-images.jianshu.io/upload_images/1179389-3867d900e9a4c18c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-27593edc-03e2-4a42-baf3-ed5e5096b3cb.png)
**配置JDK 11完成:**
![](https://upload-images.jianshu.io/upload_images/1179389-ecc8bbeb8f7f8a86.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-8526d944-a36e-4d37-93a0-9ad4ad53f927.png)
**注:** 如果这一步出错,大概率是某个软件环境未装,或者即使装了,但版本不匹配,控制台打印日志里一般是会提醒的。
比如我在配置`JDK 8`的时候,就遇到了一个`errof:GCC compiler is required`的问题:
![](https://upload-images.jianshu.io/upload_images/1179389-6cde1ab51a34b6e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-2957399f-6451-46dc-a003-76e5159265e9.png)
明明系统里已经有编译器,但还是报这个错误。通过后来修改 `jdk源码根目录/common/autoconf/generated-configure.sh`文件,将相关的两行代码注释后就配置通过了
![](https://upload-images.jianshu.io/upload_images/1179389-f07354003d229595.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-ffa10d36-3a77-48aa-ae0c-d3daf67f9a19.png)
![](https://upload-images.jianshu.io/upload_images/1179389-0e8c6c94255e19ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-a6f6e416-639e-4706-8b40-6152eb3cf85d.png)
配置完成,接下来开始执行真正的编译动作了!
......@@ -156,12 +166,12 @@
**JDK 8编译完成:**
![](https://upload-images.jianshu.io/upload_images/1179389-f61a82bb7b340811.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-89020f5a-0909-4c57-8c88-f655293a42a4.png)
**JDK 11编译完成:**
![](https://upload-images.jianshu.io/upload_images/1179389-238728a22d2ae4bf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-993fac94-2473-4f3b-9737-959510d2fe98.png)
从两张图的对比可以看出,编译`JDK 8``JDK 11`完成时在输出上还是有区别的。时间上的区别很大程度上来源于`JDK 11`的编译机配置要高不少。
......@@ -184,7 +194,7 @@
进入该目录后,可以输入`./java -version`命令验证:
![](https://upload-images.jianshu.io/upload_images/1179389-5449dc4bb0574327.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-f02dff40-f27e-476c-998b-bd6cdb5d3559.png)
其次,编译生成的成品`JDK`套装,可以在目录
......@@ -194,7 +204,7 @@
下找到,如图所示:
![](https://upload-images.jianshu.io/upload_images/1179389-1c9733b7a55a39fe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-1c781d34-776e-4acc-8d2b-b34bc59fda61.png)
其中:
......@@ -204,7 +214,7 @@
进入`j2sdk-image`目录会发现,里面的内容和我们平时从网络上下载的成品`JDK`内容一致。
![](https://upload-images.jianshu.io/upload_images/1179389-be394d2e97a6c8ae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-7b7f147e-58c9-4eb5-b407-b8984cd72e1d.png)
### **2、JDK 11的编译输出**
......@@ -219,7 +229,7 @@
下看到,进入该目录后,也可以输入`./java -version`命令验证:
![](https://upload-images.jianshu.io/upload_images/1179389-b2372c6a17726593.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-f9b55425-f308-44e8-8812-ac59b2707c81.png)
其次,编译生成的成品`JDK 11`套装,可以在目录
......@@ -229,7 +239,7 @@
下找到,如图所示:
![](https://upload-images.jianshu.io/upload_images/1179389-50d09e58e36b3aac.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-4e96858f-f681-4498-b1c4-282d317a6a32.png)
其中`jdk`目录就是编译生成的成品`JDK 11`套装。
......@@ -242,20 +252,20 @@
新建一个最最基本的`Java`工程,比如命名为`JdkTest`,目的是把我们自己编译出的`JDK`给用上。
![](https://upload-images.jianshu.io/upload_images/1179389-09ead6f932b19a36.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-2cf54b29-9b7e-46b2-8cde-4c36960aa09b.png)
我们点开`Project Structure`,选到`SDKs`选项,新添加上自己刚刚编译生成的JDK,并选为项目的JDK,看看是否能正常工作
![](https://upload-images.jianshu.io/upload_images/1179389-b7f8a14909575248.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-ad8023d0-fbb7-48b1-856e-a8818677a0a5.png)
![](https://upload-images.jianshu.io/upload_images/1179389-bca2673d926753de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-b8d87f09-6178-44c7-9572-a2852e81318d.png)
点击确定之后,我们运行之:
![](https://upload-images.jianshu.io/upload_images/1179389-d11ded14bd13a8a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-3164cf31-8078-46d7-bee0-22e05b0c08de.png)
可以看到我们自己编译出的JDK已经用上了。
......@@ -266,31 +276,31 @@
我们继续在上一步`JdkTest`项目的`Project Structure` → `SDKs`里将`JDK`源码关联到自行下载的JDK源码路径上:
![](https://upload-images.jianshu.io/upload_images/1179389-538b9a08b4031a9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-129ede68-c368-461e-92d6-38a8e5dee344.png)
这样方便我们对自己下载的`JDK源码`进行**阅读****调试****修改**、以及在源码里随意**做笔记****加注释**
举个最简单的例子,比如我们打开`System.out.println()`这个函数的底层源码:
![](https://upload-images.jianshu.io/upload_images/1179389-12247e4b15ec0936.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-c406b2a2-208a-4a54-a869-b3f526e93ccd.png)
我们随便给它修改一下,加两行简单的标记,像这样:
![](https://upload-images.jianshu.io/upload_images/1179389-b271aef7ba3844f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-2a46b215-04e5-458a-b475-4dc31d7fe326.png)
为了使我们新加的代码行生效,我们必须要重新去JDK源码的根目录中再次执行 `make images`重新编译生成JDK方可生效:
![](https://upload-images.jianshu.io/upload_images/1179389-5d35948c61ee8801.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-fd3cf88d-007e-4615-99b5-a3499c35ef40.png)
因为之前已经全量编译过了,所以再次`make`的时候增量编译一般很快。
重新编译之后,我们再次运行`JdkTest`项目,就可以看到改动的效果了:
![](https://upload-images.jianshu.io/upload_images/1179389-9e44c4851d71b82d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-33dc0de6-2690-4ba2-9fe7-0e450c44c07b.png)
* * *
......@@ -305,14 +315,14 @@
比如,还是以上面例子中最简单的`System.out.println()`源码为例,我们添加几行中文注释:
![](https://upload-images.jianshu.io/upload_images/1179389-0994ca44784b5558.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-d6a44833-b908-4824-8862-1679bfdddfa3.png)
这时候我们去JDK源码目录下编译会发现满屏类似这样的报错:
> 错误: 编码 ascii 的不可映射字符
![](https://upload-images.jianshu.io/upload_images/1179389-d830ae06a196161e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-ad0ca5a3-36c7-477d-bd58-d6731a87d762.png)
顿时有点懵,毕竟仅仅是加了几行注释。对于我们来说,源码里写点多行的中文注释基本是**刚需**,然而编译竟会报错,这还能不能让人愉快的玩耍了... 当时后背有点发凉。
......@@ -325,12 +335,12 @@
`jdk源码根目录/make/common/SetupJavaCompilers.gmk`文件中有两处指定了`ascii`相关的编码方式:
![](https://upload-images.jianshu.io/upload_images/1179389-f8819768a06f1019.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-a86933af-f6d5-4d45-b4ca-33069a212c52.png)
于是尝试将这两处`-encoding ascii`的均替换成`-encoding utf-8`
![](https://upload-images.jianshu.io/upload_images/1179389-31c43dadfdddaaec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/compile-jdk-c8117faf-d027-48d3-869b-32d0e98e8372.png)
然后再次执行`make images`编译,编译顺利通过!
......@@ -340,3 +350,7 @@
这样后面不管是**阅读****调试**还是**定制**`JDK`源码都非常方便了。
---
引用链接:https://segmentfault.com/a/1190000023251649
......@@ -31,13 +31,13 @@ tag:
先创建一个字符串,这时候"jack"有一个引用,就是 m。
![](https://upload-images.jianshu.io/upload_images/1179389-4582bb244165acc4.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-691109d2-bee4-4a79-8da6-87c5fd233f54.jpg)
然后将 m 设置为 null,这时候"jack"的引用次数就等于0了,在引用计数算法中,意味着这块内容就需要被回收了。
**m = null;**
![](https://upload-images.jianshu.io/upload_images/1179389-048b1138f853f7eb.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-74865618-4576-4f8b-baf3-17d6a71125b9.jpg)
引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。
......@@ -68,7 +68,7 @@ b = null;
**2\. 相互引用**
**3\. 置空各自的声明引用**
![](https://upload-images.jianshu.io/upload_images/1179389-86b2c41b63437f88.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-fe980c00-3605-4b5d-a711-7edbfd2c80b0.jpg)
我们可以看到,最后这2个对象已经不可能再被访问了,但由于他们相互引用着对方,导致它们的引用计数永远都不会为0,通过引用计数算法,也就永远无法通知GC收集器回收它们。
......@@ -76,7 +76,7 @@ b = null;
可达性分析算法(Reachability Analysis)的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。
![](https://upload-images.jianshu.io/upload_images/1179389-696ce5e5d3727f0b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-1636ce77-77b3-4b10-b75a-c0c2d28912c5.jpg)
通过可达性算法,成功解决了引用计数所无法解决的问题-“循环依赖”,只要你无法与 GC Root 建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些属于 GC Root。
......@@ -89,7 +89,7 @@ b = null;
* 方法区中常量引用的对象
* 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
![](https://upload-images.jianshu.io/upload_images/1179389-5b483f86d4aad226.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-6abf9f50-dc53-4e8f-a7f6-3e74df8803d6.jpg)
1、虚拟机栈(栈帧中的本地变量表)中引用的对象
此时的 s,即为 GC Root,当s置空时,localParameter 对象也断掉了与 GC Root 的引用链,将被回收。
......@@ -141,7 +141,7 @@ s = null;
4、本地方法栈中引用的对象
任何 native 接口都会使用某种本地方法栈,实现的本地方法接口是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。当线程调用 Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈。然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不再在线程的 Java 栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。
![](https://upload-images.jianshu.io/upload_images/1179389-71a5f9fe99025832.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-a138a4b4-56cb-4d6f-a65a-7f4259977476.jpg)
### 怎么回收垃圾
......@@ -149,7 +149,7 @@ s = null;
**标记 --- 清除算法**
![](https://upload-images.jianshu.io/upload_images/1179389-bafcd58836e2de69.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-2001e224-0f34-4429-bc89-a8fbe8ab271c.jpg)
标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。就像上图一样,清理掉的垃圾就变成未使用的内存区域,等待被再次使用。
......@@ -159,7 +159,7 @@ s = null;
**复制算法**
![](https://upload-images.jianshu.io/upload_images/1179389-129240ac460cb06e.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-a2b15e6f-6921-4710-bf76-77858df38c27.jpg)
复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。
......@@ -167,7 +167,7 @@ s = null;
**标记整理算法**
![](https://upload-images.jianshu.io/upload_images/1179389-d931502c610a971c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-2d47a225-ad9d-4f15-9b4d-7dce9a693adf.jpg)
标记整理算法(Mark-Compact)标记过程仍然与标记 --- 清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。
......@@ -177,7 +177,7 @@ s = null;
### 内存模型与回收策略
![](https://upload-images.jianshu.io/upload_images/1179389-11711b193fa3c146.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/gc-59dddea1-b6bc-4fd4-bb79-d81adbdc7bed.jpg)
Java 堆(Java Heap)是JVM所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,这里我们主要分析一下 Java 堆的结构。
......
......@@ -20,7 +20,7 @@ tag:
一个线程中的方法调用链可能会很长,很多方法都处于执行状态。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法成为当前方法。执行引擎运行的所有字节码指令对当前栈帧进行操作,在概念模型上,典型的栈帧结构如下图:
![](https://img-blog.csdnimg.cn/img_convert/0319e9d067853d3dc33ea392a7941be6.webp?x-oss-process=image/format,png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-a58ee82e-c0b0-4c06-9606-f7a0f0df0de9)
#### 局部变量表
......@@ -40,7 +40,7 @@ public class LocalVaraiablesTable {
然后用 Intellij IDEA 的 jclasslib 查看一下编译后的字节码文件 LocalVaraiablesTable.class。可以看到 `write()` 方法的 Code 属性中,Maximum local variables(局部变量表的最大容量)的值为 3。
![](https://upload-images.jianshu.io/upload_images/1179389-83d2c9bab74251b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-70ab6bf6-4fbb-4722-99b4-a93d5061630c.png)
按理说,局部变量表的最大容量应该为 2 才对,一个 age,一个 name,为什么是 3 呢?
......@@ -48,7 +48,7 @@ public class LocalVaraiablesTable {
点开 Code 属性,查看 LocalVaraiableTable 就可以看到详细的信息了。
![](https://upload-images.jianshu.io/upload_images/1179389-81b7f1368e494908.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-e5e6037c-9be1-472f-8ab3-2754466e7828.png)
第 0 个是 this,类型为 LocalVaraiablesTable 对象;第 1 个是方法参数 age,类型为整形 int;第 2 个是方法内部的局部变量 name,类型为字符串 String。
......@@ -97,15 +97,15 @@ public void solt() {
用 jclasslib 可以查看到,`solt()` 方法的 Maximum local variables 的值为 4。
![](https://upload-images.jianshu.io/upload_images/1179389-93f5a356e356c2b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-6734774b-376c-49bf-a915-508c7e829557.png)
为什么等于 4 呢?带上 this 也就 3 个呀?
![](https://upload-images.jianshu.io/upload_images/1179389-b0ebba7ff857d676.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-91ad04f8-1620-44c9-83d1-6fbd7860701a.png)
查看 LocalVaraiableTable 就明白了,变量 i 的下标为 3,也就意味着变量 d 占了两个槽。
![](https://upload-images.jianshu.io/upload_images/1179389-dbb0621be3cf1a9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-630b50e3-fc37-4748-8d20-852d5358f87a.png)
#### 操作数栈
......@@ -127,11 +127,11 @@ public class OperandStack {
OperandStack 类共有 2 个方法,`test()` 方法中调用了 `add()` 方法,传递了 2 个参数。用 jclasslib 可以看到,`test()` 方法的 maximum stack size 的值为 3。
![](https://upload-images.jianshu.io/upload_images/1179389-a3fc3ce51eecdbdd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-f790aa0f-d742-465b-91bf-5f143ee098c1.png)
这是因为调用成员方法的时候会将 this 和所有参数压入栈中,调用完毕后 this 和参数都会一一出栈。通过 「Bytecode」 面板可以查看到对应的字节码指令。
![](https://upload-images.jianshu.io/upload_images/1179389-cbff77396db0e12f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-c37add5c-a74b-4bd6-8c8e-9085d9e6d374.png)
- aload_0 用于将局部变量表中下标为 0 的引用类型的变量,也就是 this 加载到操作数栈中;
- iconst_1 用于将整数 1 加载到操作数栈中;
......@@ -142,14 +142,14 @@ OperandStack 类共有 2 个方法,`test()` 方法中调用了 `add()` 方法
再来看一下 `add()` 方法的字节码指令。
![](https://upload-images.jianshu.io/upload_images/1179389-1b9f4e45ca8fcacc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-49e3f396-7ea8-49f5-81d4-093b9bdfa453.png)
- iload_1 用于将局部变量表中下标为 1 的 int 类型变量加载到操作数栈上(下标为 0 的是 this);
- iload_2 用于将局部变量表中下标为 2 的 int 类型变量加载到操作数栈上;
- iadd 用于 int 类型的加法运算;
- ireturn 为返回值为 int 的方法返回指令。
![](https://upload-images.jianshu.io/upload_images/1179389-1a3949544aced120.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-3ffdbe03-c0e4-49de-97a6-76666964a087.png)
操作数中的数据类型必须与字节码指令匹配,以上面的 iadd 指令为例,该指令只能用于整形数据的加法运算,它在执行的时候,栈顶的两个数据必须是 int 类型的,不能出现一个 long 型和一个 double 型的数据进行 iadd 命令相加的情况。
......@@ -204,7 +204,7 @@ public class DynamicLinking {
用 jclasslib 看一下 main 方法的字节码指令。
![](https://upload-images.jianshu.io/upload_images/1179389-6dce7f63885221ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-93a21aaf-ff67-445d-8ddb-ac6f72fd9b25.png)
- 第 1 行:new 指令创建了一个 Man 对象,并将对象的内存地址压入栈中。
- 第 2 行:dup 指令将栈顶的值复制一份并压入栈顶。因为接下来的指令 invokespecial 会消耗掉一个当前类的引用,所以需要复制一份。
......@@ -559,7 +559,7 @@ Java 语言常被人们定义成「解释执行」的语言,但随着 JIT 以
无论是解释执行还是编译执行,无论是物理机还是虚拟机,对于应用程序,机器都不可能像人一样阅读、理解,然后获得执行能力。大部分的程序代码到物理机的目标代码或者虚拟机执行的指令之前,都需要经过下图中的各个步骤。下图中最下面的那条分支,就是传统编译原理中程序代码到目标机器代码的生成过程;中间那条分支,则是解释执行的过程。
![](https://img-blog.csdnimg.cn/a87ea7b0dd7f4bdca22d8e6732f64ee8.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/how-jvm-run-zijiema-zhiling-3c8a0865-2a77-464e-8dd6-5616fd6a72d7.png)
如今,基于物理机、Java 虚拟机或者非 Java 的其它高级语言虚拟机的语言,大多都会遵循这种基于现代编译原理的思路,在执行前先对程序源代码进行词法分析和语法分析处理,把源代码转化为抽象语法树。对于一门具体语言的实现来说,词法分析、语法分析以至后面的优化器和目标代码生成器都可以选择独立于执行引擎,形成一个完整意义的编译器去实现,这类代表是 C/C++。也可以为一个半独立的编译器,这类代表是 Java。又或者把这些步骤和执行全部封装在一个封闭的黑匣子中,如大多数的 JavaScript 执行器。
......@@ -580,5 +580,5 @@ Java 编译器输出的指令流,基本上是一种基于栈的指令集架构
----
作者:BaronTalk
链接:https://juejin.cn/post/6844903871010045960
引用链接:https://juejin.cn/post/6844903871010045960
......@@ -72,33 +72,33 @@ $ jps
### 可视化线程栈
![](https://upload-images.jianshu.io/upload_images/1179389-07afdc3c2b66fa3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-a606c6ac-1cfc-44c3-8fdf-e0eeeabbf05a.png)
### 对象直方图
`Tools -> Object Histogram`,我们可以通过对象直方图快速定位某个类型的对象的地址以供我们进一步分析
![](https://upload-images.jianshu.io/upload_images/1179389-ec4b946a0804334e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-cf39737a-7d6a-42de-b843-123cba1f96aa.png)
![](https://upload-images.jianshu.io/upload_images/1179389-120d4d54c09cb697.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-974d211c-e627-40d6-af13-9560ebae0bfa.png)
### OOP信息
我们可以根据对象地址在 `Tools -> Inspector` 获取对象的在 JVM 层的实例 `instanceOopDesc` 对象,它包括对象头 `_mark` 和 `_metadata` 以及实例信息
![](https://upload-images.jianshu.io/upload_images/1179389-0f5bcf523b13aadc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-026fb881-59a2-4e0f-ac4a-a2a7b505a707.png)
### 堆信息
我们可以通过 `Tools -> Heap Parameters` 获取堆信息,可以结合对象地址判断对象位置
![](https://upload-images.jianshu.io/upload_images/1179389-a2b9e8c740a8b717.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-3baabaf0-1681-4443-b8db-ae08128744d6.png)
### 加载类列表
我们可以通过 `Tools -> Class Browser` 来获取所有加载类列表
![](https://upload-images.jianshu.io/upload_images/1179389-413e8aa34bd714a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-7e4ebd1f-ba9c-4862-b4c3-574de5c30d6b.png)
### 元数据区
......@@ -108,18 +108,18 @@ HotSpot VM 里有一套对象专门用来存放元数据,它们包括: 
* `ConstantPool/ConstantPoolCache` 对象:每个 `InstanceKlass` 关联着一个 `ConstantPool`,作为该类型的运行时常量池。这个常量池的结构跟 Class 文件里的常量池基本上是对应的
![](https://upload-images.jianshu.io/upload_images/1179389-fe2110ec3400736a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-07d80d18-be4e-4861-bea3-291eea0ff262.png)
![](https://upload-images.jianshu.io/upload_images/1179389-2067a77695ea91d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-b7b0ebed-cd38-42b7-bebc-41090409a1db.png)
* `Method` 对象,用来描述 Java 方法的总体信息,如方法入口地址、调用/循环计数器等等
* `ConstMethod` 对象,记录着 Java 方法的不变的描述信息,包括方法名、方法的访问修饰符、**字节码**、行号表、局部变量表等等。**注意,字节码指令被分配在 `constMethodOop` 对象的内存区域的末尾**
* `MethodData` 对象,记录着 Java 方法执行时的 profile 信息,例如某方法里的某个字节码之类是否从来没遇到过 null,某个条件跳转是否总是走同一个分支,等等。这些信息在解释器(多层编译模式下也在低层的编译生成的代码里)收集,然后供给 HotSpot Server Compiler 用于做激进优化。
![](https://upload-images.jianshu.io/upload_images/1179389-06e56c814c156cfb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-59231922-9ce3-4107-ab1a-b33818cbab96.png)
![](https://upload-images.jianshu.io/upload_images/1179389-539d3ef606a97139.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-85c6fca7-2d1f-4194-bc07-fd1e2ab18632.png)
* `Symbol` 对象,对应 Class 文件常量池里的 `JVM_CONSTANT_Utf8` 类型的常量。有一个 VM 全局的 `SymbolTable` 管理着所有 `Symbol``Symbol` 由所有 Java 类所共享。
......@@ -159,23 +159,23 @@ class VMShow {
首先查看对象直方图可以找到三个 VMShow 对象
![](https://upload-images.jianshu.io/upload_images/1179389-48e27aeb30f47103.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-e5f0f200-83fd-4529-b5d6-416f9a6f626b.png)
那么如何确定这三个地址分别属于哪些变量呢?首先找静态变量,它在 JDK8 中是在 Class 对象中的,因此我们可以找它们的反向指针,如果是`java.lang.Class` 的那么就是静态变量
![](https://upload-images.jianshu.io/upload_images/1179389-2b4087bd358a0eb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-301ebfc3-c2c4-49de-946a-5d2f1660e669.png)
我们可以从 ObjTest 的 `instanceKlass` 中的镜像找到 class 对象来验证是否是该对象的 class
![](https://upload-images.jianshu.io/upload_images/1179389-95cab9181b8b77af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-f22359ab-b066-405f-a754-197ccbd36884.png)
那么成员变量和局部变量如何区分呢?成员变量会被类实例引用,而局部变量地址则在会被被放在栈区
![](https://upload-images.jianshu.io/upload_images/1179389-80bced631b4dafcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-51cb7321-fb7a-42b0-9321-edd410e3d328.png)
那么局部变量的反向指针都是 null,怎么确定它就被栈区所引用呢?我们可以看可视化线程栈
![](https://upload-images.jianshu.io/upload_images/1179389-9b97606de3b61531.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-51fb09f4-06d7-4518-8038-5dd69d765862.png)
### 分析字符串字面量存储区域
......@@ -198,7 +198,7 @@ public class StringTest {
2. 打开对象直方图发现只有 1 个 `a` 的字符串对象
![](https://upload-images.jianshu.io/upload_images/1179389-946aec17f0649e1c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-b5631e70-be3a-48de-99be-4468632d23e0.png)
3. 查找 StringTable 中 `a` 的对象地址
......@@ -208,7 +208,7 @@ jseval "st = sa.vm.stringTable;st.stringsDo(function (s) { if (sapkg.oops.OopUti
可以根据需要改变 `matches` 中的值来匹配
![](https://upload-images.jianshu.io/upload_images/1179389-27e18d823a9fbd3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-76084b8d-6134-4866-8891-c24a43a3b836.png)
可以看到这个对象地址就是 StringTable 中引用的地址
......@@ -216,7 +216,7 @@ jseval "st = sa.vm.stringTable;st.stringsDo(function (s) { if (sapkg.oops.OopUti
5. 重新使用对象直方图查看 String 值
![](https://upload-images.jianshu.io/upload_images/1179389-c6ce35bebc51e557.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-f3c35027-2fe1-4b18-8a1c-b7243a9b5149.png)
这里有5个值,`ab` 有3个:
......@@ -231,13 +231,13 @@ jseval "st = sa.vm.stringTable;st.stringsDo(function (s) { if (sapkg.oops.OopUti
```
![](https://upload-images.jianshu.io/upload_images/1179389-2953df01d3c87685.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-f07a9fc8-5744-4859-9df9-c3a1016e936a.png)
那么运行时常量池中存放的是哪些呢?实际上它和 StringTable 一样是这些对象的引用,只不过 StringTable 是全局共享的,而运行时常量池只有该类的一些字面量。我们通过加载类列表可以查看
![](https://upload-images.jianshu.io/upload_images/1179389-2dd5e0ac29c0f3a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-25fa5e06-0250-424b-8b05-aea770e80963.png)
![](https://zzcoder.oss-cn-hangzhou.aliyuncs.com/jvm/HSDB-20.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-88e8c10d-e09c-4a74-95e6-e5b21577459f.png)
### 分析String.intern
......@@ -328,22 +328,22 @@ public class StringInternTest {
jseval "st = sa.vm.stringTable;st.stringsDo(function (s) { if (sapkg.oops.OopUtilities.stringOopToString(s).matches('^(he|llo|hello|1|2|12)')) {print(s + ': ');s.printValueOn(java.lang.System.out); println('')}})"
```
![](https://upload-images.jianshu.io/upload_images/1179389-fd5799daae6fb942.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-ca5d06a0-1690-4a8f-98cb-aa6bd7800afe.png)
但是 `hello` 对象还是存在的(new)
![](https://upload-images.jianshu.io/upload_images/1179389-da8c4df5133fccd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-85253e10-d0c2-442b-a26b-91b1b2588f1c.png)
接着执行 s1.intern 会将 `hello` 对象的地址放入 StringTable
![](https://upload-images.jianshu.io/upload_images/1179389-d888e688976bae33.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-801d35f2-0c8a-4699-afbf-057c1e6cac6c.png)
再执行 `String s2="hello";` 会发现 `hello` 对象仍然只有一个,都指向同一个。
而继续在 6 打断点,即执行完 `String s4 = "12";`,因为 `12` 不在字符串常量池,那么会新建一个 `12`的实例,并让字符串常量池引用它,这样会发现就有两个 `12` 了
![](https://upload-images.jianshu.io/upload_images/1179389-7abae26e2d2e6a6b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/hsdb-47a42bfa-7645-4c9b-bcd8-aeabea1ae44f.png)
---
>参考链接:https://zzcoder.cn/2019/12/06/HSDB%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%88%98/
\ No newline at end of file
>参考链接:https://zzcoder.cn/2019/12/06/HSDB%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%88%98/
---
category:
- Java核心
- JVM
tag:
- Java
---
# Java即时编译(JIT)器原理解析及实践
## 一、导读
常见的编译型语言如C++,通常会把代码直接编译成CPU所能理解的机器码来运行。而Java为了实现“一次编译,处处运行”的特性,把编译的过程分成两部分,首先它会先由javac编译成通用的中间形式——字节码,然后再由解释器逐条将字节码解释为机器码来执行。所以在性能上,Java通常不如C++这类编译型语言。
......@@ -12,7 +22,7 @@ Java的执行过程整体可以分为两个部分,第一步由javac将源码
怎么样才会被认为是热点代码呢?JVM中会设置一个阈值,当方法或者代码块的在一定时间内的调用次数超过这个阈值时就会被编译,存入codeCache中。当下次执行时,再遇到这段代码,就会从codeCache中读取机器码,直接执行,以此来提升程序运行的性能。整体的执行过程大致如下图所示:
![image](https://upload-images.jianshu.io/upload_images/1179389-3126df91fd5df8e4.png@648w_454h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-9a62fc02-1a6a-451e-bb2b-19fc086d5be0.png)
### 1\. JVM中的编译器
......@@ -40,7 +50,7 @@ Ideal Graph的构建是在解析字节码的时候,根据字节码中的指令
无论是否进行全局优化,Ideal Graph都会被转化为一种更接近机器层面的MachNode Graph,最后编译的机器码就是从MachNode Graph中得的,生成机器码前还会有一些包括寄存器分配、窥孔优化等操作。关于Ideal Graph和各种全局的优化手段会在后面的章节详细介绍。Server Compiler编译优化的过程如下图所示:
![image](https://upload-images.jianshu.io/upload_images/1179389-9fe7776de5303554.png@823w_864h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-f4d1b763-be02-4bb2-ab0e-45b1f0eb9550.png)
**Graal Compiler**
......@@ -66,7 +76,7 @@ profiling就是收集能够反映程序执行状态的数据。其中最基本
通常情况下,C2代码的执行效率要比C1代码的高出30%以上。C1层执行的代码,按执行效率排序从高至低则是1层>2层>3层。这5个层次中,1层和4层都是终止状态,当一个方法到达终止状态后,只要编译后的代码并没有失效,那么JVM就不会再次发出该方法的编译请求的。服务实际运行时,JVM会根据服务运行情况,从解释执行开始,选择不同的编译路径,直到到达终止状态。下图中就列举了几种常见的编译路径:
![image](https://upload-images.jianshu.io/upload_images/1179389-9f661f22d9141930.png@863w_680h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-a6cebc82-ed4d-4b6d-892a-c5b245d227ab.png)
* 图中第①条路径,代表编译的一般情况,热点方法从解释执行到被3层的C1编译,最后被4层的C2编译。
* 如果方法比较小(比如Java服务中常见的getter/setter方法),3层的profiling没有收集到有价值的数据,JVM就会断定该方法对于C1代码和C2代码的执行效率相同,就会执行图中第②条路径。在这种情况下,JVM会在3层编译之后,放弃进入C2编译,直接选择用1层的C1编译运行。
......@@ -82,7 +92,7 @@ Java虚拟机根据方法的调用次数以及循环回边的执行次数来触
循环回边
```
```java
public void nlp(Object obj) {
int sum = 0;
for (int i = 0; i < 200; i++) {
......@@ -179,7 +189,7 @@ Plain Text
DeadCodeElimination
```
```java
public void DeadCodeElimination{
int a = 2;
int b = 0
......@@ -212,7 +222,7 @@ add(a,b)
DeadCodeElimination
```
```java
public void DeadCodeElimination{
int a = 1;
int b = 0;
......@@ -229,7 +239,7 @@ public void DeadCodeElimination{
HIR是由很多基本块(Basic Block)组成的控制流图结构,每个块包含很多SSA形式的指令。基本块的结构如下图所示:
![image](https://upload-images.jianshu.io/upload_images/1179389-11a9e073b3bcb0b7.png@262w_611h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-037b406d-1040-4bf8-976c-abf14a92402d.png)
其中,predecessors表示前驱基本块(由于前驱可能是多个,所以是BlockList结构,是多个BlockBegin组成的可扩容数组)。同样,successors表示多个后继基本块BlockEnd。除了这两部分就是主体块,里面包含程序执行的指令和一个next指针,指向下一个执行的主体块。
......@@ -257,7 +267,7 @@ C2编译器中的Ideal Graph采用的是一种名为Sea-of-Nodes中间表达形
example
```
```java
public static int foo(int count) {
int sum = 0;
for (int i = 0; i < count; i++) {
......@@ -270,7 +280,7 @@ public static int foo(int count) {
对应的IR图如下所示:
![image](https://upload-images.jianshu.io/upload_images/1179389-d8aead91e1ba9a44.png@1368w_1260h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-f96da42a-568b-45ba-bed1-f4238ac32e14.png)
图中若干个顺序执行的节点将被包含在同一个基本块之中,如图中的B0、B1等。B0基本块中0号Start节点是方法入口,B3中21号Return节点是方法出口。红色加粗线条为控制流,蓝色线条为数据流,而其他颜色的线条则是特殊的控制流或数据流。被控制流边所连接的是固定节点,其他的则是浮动节点(浮动节点指只要能满足数据依赖关系,可以放在不同位置的节点,浮动节点变动的这个过程称为Schedule)。
......@@ -284,7 +294,7 @@ Ideal Graph是SSA IR。 由于没有变量的概念,这会带来一个问题
example
```
```java
int test(int x) {
int a = 0;
if(x == 1) {
......@@ -299,7 +309,7 @@ int a = 0;
为了解决这个问题,就引入一个Phi Nodes的概念,能够根据不同的执行路径选择不同的值。于是,上面这段代码可以表示为下面这张图:
![image](https://upload-images.jianshu.io/upload_images/1179389-3e627d4775f58424.png@631w_930h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-fb8b2bac-a7b9-45eb-bd28-05e35cf043ae.png)
Phi Nodes中保存不同路径上包含的所有值,Region Nodes根据不同路径的判断条件,从Phi Nodes取得当前执行路径中变量应该赋予的值,带有Phi节点的SSA形式的伪代码如下:
......@@ -348,7 +358,7 @@ Java服务中存在大量getter/setter方法,如果没有方法内联,在调
方法内联的过程
```
```java
public static boolean flag = true;
public static int value0 = 0;
public static int value1 = 1;
......@@ -370,11 +380,11 @@ public static int bar(boolean flag) {
bar方法的IR图:
![image](https://upload-images.jianshu.io/upload_images/1179389-01e951d9001258a7.png@794w_480h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-04ca4a7e-46e7-4782-bb43-333aea31ed57.png)
内联后的IR图:
![image](https://upload-images.jianshu.io/upload_images/1179389-ab152f0a9b85b528.png@802w_1202h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-4bf4d190-7fd2-4542-b948-0c85ee6963d2.png)
内联不仅将被调用方法的IR图节点复制到调用者方法的IR图中,还要完成其他操作。
......@@ -390,7 +400,7 @@ bar方法的IR图:
可以通过虚拟机参数-XX:MaxInlineLevel调整内联的层数,以及1层的直接递归调用(可以通过虚拟机参数-XX:MaxRecursiveInlineLevel调整)。一些常见的内联相关的参数如下表所示:
![image](https://upload-images.jianshu.io/upload_images/1179389-b806c277c64a7bc5.png@1350w_612h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-48e4ff65-07ec-487e-8b08-2f8fed1e56bd.png)
**虚函数内联**
......@@ -400,7 +410,7 @@ C2编译器已经足够智能,能够检测这种情况并会对虚调用进行
virtual call
```
```java
public class SimpleInliningTest
{
public static void main(String[] args) throws InterruptedException {
......@@ -449,7 +459,7 @@ public class SimpleInliningTest
多实现的虚调用
```
```java
public class SimpleInliningTest
{
public static void main(String[] args) throws InterruptedException {
......@@ -515,7 +525,7 @@ Graal编译器针对这种情况,会去收集这部分执行的信息,比如
逃逸分析通常是在方法内联的基础上进行的,即时编译器可以根据逃逸分析的结果进行诸如锁消除、栈上分配以及标量替换的优化。下面这段代码的就是对象未逃逸的例子:
```
```java
pulbic class Example{
public static void main(String[] args) {
example();
......@@ -553,7 +563,7 @@ pulbic class Example{
标量替换
```
```java
public class Example{
@AllArgsConstructor
class Cat{
......@@ -570,7 +580,7 @@ public class Example{
经过逃逸分析,cat对象未逃逸出example()的调用,因此可以对聚合量cat进行分解,得到两个标量age和weight,进行标量替换后的伪代码:
```
```java
public class Example{
@AllArgsConstructor
class Cat{
......@@ -602,7 +612,7 @@ public class Example{
循环展开
```
```java
public void loopRolling(){
for(int i = 0;i<200;i++){
delete(i);
......@@ -615,7 +625,7 @@ public void loopRolling(){
循环展开
```
```java
public void loopRolling(){
for(int i = 0;i<200;i+=5){
delete(i);
......@@ -636,7 +646,7 @@ public void loopRolling(){
循环分离
```
```java
int a = 10;
for(int i = 0;i<10;i++){
b[i] = x[i] + x[a];
......@@ -649,7 +659,7 @@ for(int i = 0;i<10;i++){
循环分离
```
```java
b[0] = x[0] + 10;
for(int i = 1;i<10;i++){
b[i] = x[i] + x[i-1];
......@@ -698,13 +708,13 @@ y1=x1*3 经过强度削减后得到 y1=(x1<<1)+x1
通过增加-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining -XX:+PrintCodeCache -XX:+PrintCodeCacheOnCompilation -XX:+TraceClassLoading -XX:+LogCompilation -XX:LogFile=LogPath参数可以输出编译、内联、codeCache信息到文件。但是打印的编译日志多且复杂很难直接从其中得到信息,可以使用JITwatch的工具来分析编译日志。JITwatch首页的Open Log选中日志文件,点击Start就可以开始分析日志。
![image](https://upload-images.jianshu.io/upload_images/1179389-6f00cfb06f13f05b.png@3360w_2099h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-82ee887c-af7d-48d7-88a0-28960e564d4a.png)
![image](https://upload-images.jianshu.io/upload_images/1179389-8adb46d70ada91b6.png@3360w_2033h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-6158d832-9a0d-4af0-96ff-bf216a9cd5c6.png)
如上图所示,区域1中是整个项目Java Class包括引入的第三方依赖;区域2是功能区Timeline以图形的形式展示JIT编译的时间轴,Histo是直方图展示一些信息,TopList里面是编译中产生的一些对象和数据的排序,Cache是空闲codeCache空间,NMethod是Native方法,Threads是JIT编译的线程;区域3是JITwatch对日志分析结果的展示,其中Suggestions中会给出一些代码优化的建议,举个例子,如下图中:
![image](https://upload-images.jianshu.io/upload_images/1179389-4a2c46878663f5b7.png@1920w_218h_80q?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/jit-04b2d9ea-7add-4ee5-bf72-61a6bbaa58cf.png)
我们可以看到在调用ZipInputStream的read方法时,因为该方法没有被标记为热点方法,同时又“太大了”,导致无法被内联到。使用-XX:CompileCommand中inline指令可以强制方法进行内联,不过还是建议谨慎使用,除非确定某个方法内联会带来不少的性能提升,否则不建议使用,并且过多使用对编译线程和codeCache都会带来不小的压力。
......@@ -738,4 +748,6 @@ Graal编译器的优化方式更加激进,因此在启动时会进行更多的
本文主要介绍了JIT即时编译的原理以及在美团一些实践的经验,还有最前沿的即时编译器的使用效果。作为一项解释型语言中提升性能的技术,JIT已经比较成熟了,在很多语言中都有使用。对于Java服务,JVM本身已经做了足够多,但是我们还应该不断深入了解JIT的优化原理和最新的编译技术,从而弥补JIT的劣势,提升Java服务的性能,不断追求卓越。
原文链接:https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html
\ No newline at end of file
-----
原文链接:https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html
......@@ -10,7 +10,7 @@ tag:
在谈 JVM 内存区域划分之前,我们先来看一下 Java 程序的具体执行过程,我画了一幅图。
![](https://img-blog.csdnimg.cn/img_convert/77f1fa00cdd92c77330538642d9ca3c4.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/neicun-jiegou-dac0f4c1-8a7e-4309-a599-5664cdaf5016.png)
Java 源代码文件经过编译器编译后生成字节码文件,然后交给 JVM 的类加载器,加载完毕后,交给执行引擎执行。在整个执行的过程中,JVM 会用一块空间来存储程序执行期间需要用到的数据,这块空间一般被称为运行时数据区,也就是常说的 JVM 内存。
......@@ -28,7 +28,7 @@ Java 源代码文件经过编译器编译后生成字节码文件,然后交给
根据第二章 Java 虚拟机结构中的规定,运行时数据区可以分为以下几个部分,见下图。
![](https://img-blog.csdnimg.cn/img_convert/ed3ca24f1f857bf23a9b5c061535d258.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/neicun-jiegou-e33179f3-275b-44c9-87f6-802198f8f360.png)
### 01、程序计数器
......@@ -51,7 +51,7 @@ Java 虚拟机栈中是一个个栈帧,每个栈帧对应一个被调用的方
栈帧包含以下 5 个部分,见下图。
![](https://img-blog.csdnimg.cn/img_convert/2e727f1048988c8ce7e6ae46b08dfb54.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/neicun-jiegou-4ea2a60a-05df-4ed1-8109-99ae23acefd1.png)
[Java 虚拟机栈](https://tobebetterjavaer.com/jvm/how-jvm-run-zijiema-zhiling.md)
......@@ -100,4 +100,4 @@ JDK 8 的时候,HotSpot 移除了永久代,也就是说方法区不存在了
第一,永久代放在 Java 虚拟机中,就会受到 Java 虚拟机内存大小的限制,而元空间使用的是本地内存,也就脱离了 Java 虚拟机内存的限制。
第二,JDK 8 的时候,在 HotSpot 中融合了 JRockit 虚拟机,而 JRockit 中并没有永久代的概念,因此新的 HotSpot 就没有必要再开辟一块空间来作为永久代了。
\ No newline at end of file
第二,JDK 8 的时候,在 HotSpot 中融合了 JRockit 虚拟机,而 JRockit 中并没有永久代的概念,因此新的 HotSpot 就没有必要再开辟一块空间来作为永久代了。
......@@ -12,7 +12,7 @@ tag:
在JDK的bin目录下有很多命令行工具:
![image](https://upload-images.jianshu.io/upload_images/1179389-6298ada851edf7c6.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-547b1b2c-9fb4-4d1d-9c72-013ec210f6a5.jpg)
  我们可以看到各个工具的大小基本上都稳定在27kb左右,这个不是JDK开发团队刻意为之的,而是因为这些工具大多数是 `jdk\lib\tools.jar` 类库的一层薄包装而已,他们的主要功能代码是在tools类库中实现的。
......@@ -50,21 +50,21 @@ JAVA Dump就是虚拟机运行时的快照,将虚拟机运行时的状态和
显示当前所有java进程pid的命令,我们可以通过这个命令来查看到底启动了几个java进程(因为每一个java程序都会独占一个java虚拟机实例),不过jps有个缺点是只能显示当前用户的进程id,要显示其他用户的还只能用linux的ps命令。
![image](https://upload-images.jianshu.io/upload_images/1179389-2d5d658c8b8db0b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-2017daf6-832a-4673-b776-ad3380e47402.png)
执行jps命令,会列出所有正在运行的java进程,其中jps命令也是一个java程序。前面的数字就是进程的id,这个id的作用非常大,后面会有相关介绍。
**jps -help:**
![image](https://upload-images.jianshu.io/upload_images/1179389-99d21062099dd578.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-031be661-e47e-44f0-9e33-34368b187662.png)
**jps -l** 输出应用程序main.class的完整package名或者应用程序jar文件完整路径名
![image](https://upload-images.jianshu.io/upload_images/1179389-d9f3ff6819b15a66.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-0ccc96dc-8053-4222-9824-b116f02776a4.png)
**jps -v** 输出传递给JVM的参数
![image](https://upload-images.jianshu.io/upload_images/1179389-3bcfc37630242cb0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-059a3285-4a01-4f7a-a6ed-1cc5dcbf3f18.png)
**jps失效**
......@@ -87,7 +87,7 @@ java程序启动后,会在目录/tmp/hsperfdata_{userName}/下生成几个文
主要用于生成指定进程当前时刻的线程快照,线程快照是当前java虚拟机每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致长时间等待。
![image](https://upload-images.jianshu.io/upload_images/1179389-5f09162f867d044e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-e80d0925-2dcf-4204-b46d-47312df2a673.png)
**3、jmap**
......@@ -97,21 +97,21 @@ java程序启动后,会在目录/tmp/hsperfdata_{userName}/下生成几个文
jmap的用法摘要:
![image](https://upload-images.jianshu.io/upload_images/1179389-ef4aa428f2519317.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-96a70bab-5cee-4068-8ccb-1d35124abeea.png)
**1、`jmap pid`**
![image](https://upload-images.jianshu.io/upload_images/1179389-b98c8d50df75dff0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-38d5c9da-e433-43d2-b1bc-3f3634e05497.png)
打印的信息分别为:共享对象的起始地址、映射大小、共享对象路径的全程。
**2、`jmap -heap pid`:查看堆使用情况**
 ![image](https://upload-images.jianshu.io/upload_images/1179389-e3294292dc504803.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-75acf4c8-393d-43d1-b208-04de1f0ba6bd.png)
**3、`jmap -histo pid`:查看堆中对象数量和大小**
**![image](https://upload-images.jianshu.io/upload_images/1179389-b5c31231270ec757.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")**
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-5e42fe47-e1e6-4649-acb5-e17bd277a771.png)
打印的信息分别是:序列号、对象的数量、这些对象的内存占用大小、这些对象所属的类的全限定名
......@@ -141,7 +141,7 @@ count:打印次数
**1、jstat -gc PID 5000 20**
![image](https://upload-images.jianshu.io/upload_images/1179389-d438cd37f68605c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-3f71397d-3ff6-430d-adf4-ff5ab9f111d5.png)
S0C:年轻代第一个survivor的容量(字节)
......@@ -173,7 +173,7 @@ FGCT:从应用程序启动到采样时老年代中GC所使用的时间(单
**2、jstat -gcutil PID 5000 20**
![image](https://upload-images.jianshu.io/upload_images/1179389-a210f6badcc528ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-c2a84c1d-e853-482a-88a5-27ef39da66a0.png)
s0:年轻代中第一个survivor已使用的占当前容量百分比
......@@ -193,7 +193,7 @@ P:永久代中已使用的占当前容量百分比
**jhat heapdump**
![image](https://upload-images.jianshu.io/upload_images/1179389-83746a41d4bf74d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-fd76ac30-53a5-4549-8206-18283f330758.png)
这个命令将heapdump文件转换成html格式,并且启动一个http服务,默认端口为7000。
......@@ -201,7 +201,7 @@ P:永久代中已使用的占当前容量百分比
下面我们来访问下:ip:port
![image](https://upload-images.jianshu.io/upload_images/1179389-cf3e93133f846884.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-059e61f1-8263-4ee0-b36b-f117ecaf0a07.png)
## 6、jinfo
......@@ -219,15 +219,15 @@ jinfo可以用来查看正在运行的java运用程序的扩展参数,甚至
下面的命令显示了新生代对象晋升到老年代对象的最大年龄。在运行程序运行时并没有指定这个参数,但是通过jinfo,可以查看这个参数的当前的值。
![image](https://upload-images.jianshu.io/upload_images/1179389-31d29381b1ed517c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-f37517b7-20b4-4243-ae03-d41126ae43e5.png)
下面的命令显示是否打印gc详细信息:
![image](https://upload-images.jianshu.io/upload_images/1179389-4cbebf30779aa5ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-86c5ace2-7377-4d5a-a780-0a194e14c9a0.png)
下面的命令在运用程序运行时动态打开打印详细gc信息开关:
![image](https://upload-images.jianshu.io/upload_images/1179389-6d011dce3e04c0a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-d258d260-65eb-48f9-8585-6bed74de5a47.png)
注意事项:jinfo虽然可以在java程序运行时动态地修改虚拟机参数,但并不是所有的参数都支持动态修改。
......@@ -236,11 +236,11 @@ jinfo可以用来查看正在运行的java运用程序的扩展参数,甚至
在JDK 1.7之后,新增了一个命令行工具jcmd。它是一个多功能工具,可以用来导出堆,查看java进程,导出线程信息,执行GC等。jcmd拥有jmap的大部分功能,Oracle官方建议使用jcmd代替jmap。
使用 jcmd -l 命令列出当前运行的所有虚拟机,示例:
![image](https://upload-images.jianshu.io/upload_images/1179389-428e5bb14754ca0c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-4fa6915b-d39c-4d6d-a6e7-edc989cac76f.png)
针对每一个虚拟机,可以使用help命令列出该虚拟机支持的所有命令,示例:
![image](https://upload-images.jianshu.io/upload_images/1179389-52c559995e3fbb49.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-219b7cac-c9a9-4d47-8ecf-93a4a04fc1db.png)
子命令含义:
......@@ -264,7 +264,7 @@ jinfo可以用来查看正在运行的java运用程序的扩展参数,甚至
示例:
![image](https://upload-images.jianshu.io/upload_images/1179389-9225121e737ab406.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-b0742677-4ad0-4fd3-b985-054238af8865.png)
## 8、可视化监控工具(JConsole、JVisualVM)
......@@ -274,11 +274,11 @@ JVisualVM比JConsole更强大:支持对CPU、内存运行进行采样、配置
JConsole监控页面示例:
![image](https://upload-images.jianshu.io/upload_images/1179389-92e66778004bf548.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-7fa6b7b2-28bf-46cb-8d52-5fe3cb75240a.png)
JVisualVM监控页面示例:
![image](https://upload-images.jianshu.io/upload_images/1179389-57562c975c5c0a24.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "点击全屏显示")
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-dca6e1d0-f345-4e21-83b3-e0cf82e3d79a.png)
## 其他工具 
......@@ -325,7 +325,7 @@ strace:跟踪程序运行过程发起的系统调用
https://fastthread.io:线程栈分析的网站
![](https://upload-images.jianshu.io/upload_images/1179389-c82f746ad11b7f13.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/problem-tools-6d57b323-9665-4453-9fee-ea3111ad8629.png)
## 上问题排查思路(八股)
......@@ -339,4 +339,4 @@ CPU使用情况:top 命令
Java程序问题分析:jmap 分析堆内存、jstack 分析线程栈等,见前文。
原文链接:https://www.cnblogs.com/z-sm/p/6745375.html
\ No newline at end of file
原文链接:https://www.cnblogs.com/z-sm/p/6745375.html
......@@ -16,27 +16,27 @@ Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,此
垃圾回收的第一步是标记。垃圾回收器此时会找出内存哪些在使用中,哪些不是。
![](https://upload-images.jianshu.io/upload_images/1179389-06e52d190ca89579.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-9858785a-c6aa-4d6d-a6cd-640d24dd27d0.png)
上图中,蓝色表示已引用对象,橙色表示未引用对象。垃圾回收器要检查完所有的对象,才能知道哪些有被引用,哪些没。如果系统里所有的对象都要检查,那这一步可能会相当耗时间。
垃圾回收的第二步是清除,这一步会删掉标记出的未引用对象。
![](https://upload-images.jianshu.io/upload_images/1179389-b904f98d9c3a4097.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-768f5a2c-6c81-4f76-b847-a41cc8413228.png)
内存分配器会保留指向可用内存中的引用,以分配给新的对象。
垃圾回收的第三步是压缩,为了提升性能,删除了未引用对象后,还可以将剩下的已引用对象放在一起(压缩),这样就能更简单快捷地分配新对象了。
![](https://upload-images.jianshu.io/upload_images/1179389-841ed7d6d46c1a60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-989889b6-adb4-4277-8c67-73d76658f744.png)
之前提到过,逐一标记和压缩  Java 虚拟机中的所有对象非常低效:分配的对象越多,垃圾回收需要的时间就越久。不过,根据统计,大部分的对象,其实用没多久就不用了。
来看个例子吧。下图中,竖轴代表已分配的字节,而横轴代表程序的运行时间。
![](https://upload-images.jianshu.io/upload_images/1179389-440e4e7cffe1297c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-24b154be-4ad0-4cc7-87e9-a3035bc9e3c5.png)
可见,存活(没被释放)的对象随着运行时间越来越少。图中左侧的峰值,也表明了大部分对象其实都挺短命的。
......@@ -47,7 +47,7 @@ Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,此
根据之前的规律,就可以用来提升 JVM 的效率了。方法是,把堆分成几个部分(就是所谓的分代),分别是新生代、老年代,以及永生代。
![](https://upload-images.jianshu.io/upload_images/1179389-435daa6581893431.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-590c5011-48c4-4543-bd26-6f14c2b8614b.png)
新对象会被分配在新生代内存。一旦新生代内存满了,就会开始对死掉的对象,进行所谓的小型垃圾回收(Minor GC)过程。一片新生代内存里,死掉的越多,回收过程就越快;至于那些还活着的对象,此时就会老化,并最终老到进入老年代内存。
......@@ -63,37 +63,37 @@ Major GC 也会触发STW(Stop the World)。通常,Major GC会慢很多,
首先,将任何新对象分配给 eden 空间。 两个 survivor 空间都是空的。
![](https://upload-images.jianshu.io/upload_images/1179389-85d41f8ee21e3990?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-efe9657b-c7a6-48a8-9037-0e709b1d236c)
当 eden 空间填满时,会触发轻微的垃圾收集。
![](https://upload-images.jianshu.io/upload_images/1179389-4459380342500664?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-2497947b-92b5-4a7c-9399-1909a3153660)
引用的对象被移动到第一个 survivor 空间。 清除 eden 空间时,将删除未引用的对象。
![](https://upload-images.jianshu.io/upload_images/1179389-453df479acefff20?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-2b431315-26fa-4ea0-843a-c63ca568f960)
在下一次Minor GC中,Eden区也会做同样的操作。删除未被引用的对象,并将被引用的对象移动到Survivor区。然而,这里,他们被移动到了第二个Survivor区(S1)。
此外,第一个Survivor区(S0)中,在上一次Minor GC幸存的对象,会增加年龄,并被移动到S1中。待所有幸存对象都被移动到S1后,S0和Eden区都会被清空。注意,Survivor区中有了不同年龄的对象。
![](https://upload-images.jianshu.io/upload_images/1179389-0528ede92cfd3ae8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-e2560f59-9b24-4d16-88db-b6ac4d0b6ffe)
在下一次Minor GC中,会重复同样的操作。不过,这一次Survivor区会交换。被引用的对象移动到S0,。幸存的对象增加年龄。Eden区和S1被清空。
![](https://upload-images.jianshu.io/upload_images/1179389-e01781f12e2759f7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-aa9f883a-12db-4c8b-8391-3c289b53d804)
 此幻灯片演示了 promotion。 在较小的GC之后,当老化的物体达到一定的年龄阈值(在该示例中为8)时,它们从年轻一代晋升到老一代。
![](https://upload-images.jianshu.io/upload_images/1179389-2902b81426552307?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-dec96816-2912-4127-aaaa-a4d987123f52)
随着较小的GC持续发生,物体将继续被推广到老一代空间。
![](https://upload-images.jianshu.io/upload_images/1179389-b8b67001c0b138a7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-6cb31f8a-2eac-489c-88bd-fc643996ab49)
所以这几乎涵盖了年轻一代的整个过程。 最终,将主要对老一代进行GC,清理并最终压缩该空间。
![](https://upload-images.jianshu.io/upload_images/1179389-1a7e07eeff15e6e7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-df98a004-e233-4fb5-a31a-f422033ecfa7)
--------
......@@ -101,7 +101,7 @@ Major GC 也会触发STW(Stop the World)。通常,Major GC会慢很多,
Java 堆(Java Heap)是 JVM 所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,这里我们主要分析一下 Java 堆的结构。
![](https://upload-images.jianshu.io/upload_images/1179389-864ad362ade36520.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/tujie-gc-294701a5-1c50-4112-94a1-96a8bab80e34.png)
Java 堆主要分为 2 个区域-年轻代与老年代,其中年轻代又分 Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2 个区。可能这时候大家会有疑问,为什么需要 Survivor 区,为什么 Survivor 还要分 2 个区。
......@@ -134,4 +134,4 @@ Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交
----
参考链接:https://mp.weixin.qq.com/s/RQGImK3-SrvJfs8eYCiv4A
\ No newline at end of file
参考链接:https://mp.weixin.qq.com/s/RQGImK3-SrvJfs8eYCiv4A
......@@ -39,7 +39,7 @@ Java 字节码由操作码和操作数组成。
x 为操作码助记符,表明是哪一种数据类型。见下表所示。
![](https://img-blog.csdnimg.cn/img_convert/cb7b6b29c419c1437c40282cbc8ce4fe.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-879da2f2-fb72-48a9-985e-5a28a9fc8814.png)
像 arraylength 指令,没有操作码助记符,它没有代表数据类型的特殊字符,但操作数只能是一个数组类型的对象。
......@@ -55,7 +55,7 @@ private void load(int age, String name, long birthday, boolean sex) {
通过 jclasslib 看一下 `load()` 方法(4 个参数)的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/733a2b75879ba59a36e58911cfe2c45d.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-05bfae95-2a33-402c-9041-570093729c42.png)
- iload_1:将局部变量表中下标为 1 的 int 变量压入操作数栈中。
- aload_2:将局部变量表中下标为 2 的引用数据类型变量(此时为 String)压入操作数栈中。
......@@ -64,7 +64,7 @@ private void load(int age, String name, long birthday, boolean sex) {
通过查看局部变量表就能关联上了。
![](https://img-blog.csdnimg.cn/img_convert/4560f520a117f4ba77ae3563079009be.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-79d74946-ce9e-41d4-b889-bda861f847bc.png)
**2)将常量池中的常量压入操作数栈中**
......@@ -73,7 +73,7 @@ private void load(int age, String name, long birthday, boolean sex) {
**const 系列**,用于特殊的常量入栈,要入栈的常量隐含在指令本身。
![](https://img-blog.csdnimg.cn/img_convert/9b7ae2382755aeb504ddad064d8964af.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-270c314d-872b-43b0-861f-417eafc046fd.png)
**push 系列**,主要包括 bipush 和 sipush,前者接收 8 位整数作为参数,后者接收 16 位整数。
......@@ -101,7 +101,7 @@ public void pushConstLdc() {
通过 jclasslib 看一下 `pushConstLdc()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/b69c0cca77a38f3baba7abac254576a4.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-b34fc802-18bb-46a1-8d24-de2087c9b6bf.png)
- iconst_m1:将 -1 入栈。范围 [-1,5]。
- bipush 127:将 127 入栈。范围 [-128,127]。
......@@ -136,14 +136,14 @@ public void store(int age, String name) {
通过 jclasslib 看一下 `store()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/c6eb018c14edaee964a8c2e3116291c1.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-d955468c-d07d-47cd-b82b-c03ecea8753d.png)
- istore_3:从操作数中弹出一个整数,并把它赋值给局部变量表中索引为 3 的变量。
- astore 4:从操作数中弹出一个引用数据类型,并把它赋值给局部变量表中索引为 4 的变量。
通过查看局部变量表就能关联上了。
![](https://img-blog.csdnimg.cn/img_convert/bde3547dc74375477315d5dfafcd0e1e.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-a08c20cb-c148-47c9-91e2-df37e68989a9.png)
### 02、算术指令
......@@ -200,7 +200,7 @@ public void calculate(int age) {
通过 jclasslib 看一下 `calculate()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/45314e447b9f9fac2ccf873aa00ca5ab.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-598e4204-fd77-425b-b536-1e001cda8e13.png)
- iadd,加法
- isub,减法
......@@ -238,7 +238,7 @@ public void updown() {
通过 jclasslib 看一下 `updown()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/309c72bbe9c10144b78570ab08797710.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-0c3e47c6-1e25-4926-a838-20cf146a8993.png)
- i2d,int 宽化为 double
- f2l, float 窄化为 long
......@@ -269,7 +269,7 @@ public void newObject() {
通过 jclasslib 看一下 `newObject()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/425c6162880081c0ab797a572ff6354a.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-8125da3d-876c-43fe-8347-cb2341408088.png)
- `new #13 <java/lang/String>`,创建一个 String 对象。
......@@ -304,7 +304,7 @@ public class Writer {
通过 jclasslib 看一下 `main()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/91746596e52cdd365b93a835aa27e67b.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-70441cfc-7c6e-4a5e-b0dd-818fc3fa1a67.png)
- `getstatic #2 <com/itwanger/jvm/Writer.mark>`,访问静态变量 mark
- `getfield #6 <com/itwanger/jvm/Writer.name>`,访问成员变量 name
......@@ -428,7 +428,7 @@ invokestatic #11 // Method print:()V
方法返回指令根据方法的返回值类型进行区分,常见的返回指令见下图。
![](https://img-blog.csdnimg.cn/img_convert/4a465d6c2ae7de70e1a1f12d8a40ac25.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-37513fa2-fdba-45db-adfc-c18225c6ff8b.png)
### 06、操作数栈管理指令
......@@ -453,7 +453,7 @@ public class Dup {
通过 jclasslib 看一下 `incAndGet()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/402875d4c69a320984350692132d683d.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-642ca54e-5808-428d-9840-ebf478e95c17.png)
- aload_0:将 this 入栈。
- dup:复制栈顶的 this。
......@@ -490,13 +490,13 @@ public void lcmp(long a, long b) {
通过 jclasslib 看一下 `lcmp()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/2f46f58d1429d54e6d09e7fcc53d72ce.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-e8fa6685-b3d4-4f42-8fc5-8a4d8a9efe7b.png)
lcmp 用于两个 long 型的数据进行比较。
**2)条件跳转指令**
![](https://img-blog.csdnimg.cn/img_convert/909f03d1ae0b08bd64643358706156ac.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-5de34f26-52ad-4e07-a20d-91ea92038984.png)
这些指令都会接收两个字节的操作数,它们的统一含义是,弹出栈顶元素,测试它是否满足某一条件,满足的话,跳转到对应位置。
......@@ -519,13 +519,13 @@ public void fi() {
通过 jclasslib 看一下 `fi()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/f35d01d4ae60032f86993f46134f155b.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-d0561d5c-ae21-48e7-9e7c-4aae87d02f56.png)
`3 ifne 12 (+9)` 的意思是,如果栈顶的元素不等于 0,跳转到第 12(3+9)行 `12 bipush 20`
**3)比较条件转指令**
![](https://img-blog.csdnimg.cn/img_convert/84f3fc2310616ffbe9fad460d7f75325.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-bfab6edd-d63f-45a7-8838-997e7630fa2a.png)
前缀“if_”后,以字符“i”开头的指令针对 int 型整数进行操作,以字符“a”开头的指令表示对象的比较。
......@@ -541,7 +541,7 @@ public void compare() {
通过 jclasslib 看一下 `compare()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/9f9e9e9a24d7e62622fa5d13be4f316a.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-d4f9a680-1364-4af9-9474-c0763c9bc6f7.png)
`11 if_icmple 18 (+7)` 的意思是,如果栈顶的两个 int 类型的数值比较的话,如果前者小于后者时跳转到第 18 行(11+7)。
......@@ -571,7 +571,7 @@ public void switchTest(int select) {
通过 jclasslib 看一下 `switchTest()` 方法的字节码指令。
![](https://img-blog.csdnimg.cn/img_convert/13f65030f2bd0ad8d0c8a631c47fae14.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zijiema-zhiling-04e166ae-13c7-4025-804a-be88e2923a50.png)
case 2 的时候没有 break,所以 case 2 和 case 3 是连续的,用的是 tableswitch。如果等于 1,跳转到 28 行;如果等于 2 和 3,跳转到 34 行,如果是 default,跳转到 40 行。
......
......@@ -44,7 +44,7 @@ Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode) # 使用的是H
## 二、Java 内存区域
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/java虚拟机运行时数据区.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-1d779fca-b6a4-4982-b746-2a8db7805645.png)
### 2.1 程序计数器
......@@ -88,7 +88,7 @@ Java 堆(Java Heap)是虚拟机所管理的最大一块的内存空间,它
+ **指针碰撞**:假设 Java 堆中内存是绝对规整的,所有使用的内存放在一边,所有未被使用的内存放在另外一边,中间以指针作为分界点指示器。此时内存分配只是将指针向空闲方向偏移出对象大小的空间即可,这种方式被称为指针碰撞。
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_指针碰撞.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-afb11e2b-f457-4a19-bb21-f659756061ec.png)
+ **空闲列表**:如果 Java 堆不是规整的,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,哪些是不可用的。在进行内存分配时,只需要从该列表中选取出一块足够的内存空间划分给对象实例即可。
......@@ -136,12 +136,12 @@ Java 堆(Java Heap)是虚拟机所管理的最大一块的内存空间,它
通过句柄访问对象:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_通过句柄访问对象.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-f6b5eb22-a5af-40c0-8c80-00fdd6d16b1d.png)
通过直接指针访问对象:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_通过直接指针访问对象.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-f696f4a8-af51-4e28-9d72-c2f6b1e5b3db.png)
句柄访问的优点在于对象移动时(垃圾收集时移动对象是非常普遍的行为)只需要改变句柄中实例数据的指针,而 `reference` 本生并不需要修改;指针访问则反之,由于其 `reference` 中存储的直接就是对象地址,所以当对象移动时, `reference` 需要被修改。但针对只需要访问对象本身的场景,指针访问则可以减少一次定位开销。由于对象访问是一项非常频繁的操作,所以这类减少的效果会非常显著,基于这个原因,HotSpot 主要使用的是指针访问的方式。
......@@ -172,7 +172,7 @@ System.gc();
上面的代码在大多数虚拟机中都能被正确的回收,因为大多数主流的虚拟机都是采用的可达性分析方法来判断对象是否死亡。可达性分析是通过一系列被称为 `GC Roots` 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为引用链(Reference Chain),如果某个对象到 `GC Roots` 间没有任何引用链相连,这代表 `GC Roots` 到该对象不可达, 此时证明此该对象不可能再被使用。
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_可达性分析.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-f1325549-5689-4398-a39c-c0a6836f6077.png)
在 Java 语言中,固定可作为 `GC Roots` 的对象包括以下几种:
......@@ -233,7 +233,7 @@ System.gc();
它是最基础的垃圾收集算法,收集过程分为两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象;也可以反过来,标记存活对象,统一回收所有未被标记的对象。
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_标记清除算法.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-7d489254-f1e0-4feb-bd4a-af129767a787.png)
它主要有以下两个缺点:
......@@ -248,7 +248,7 @@ System.gc();
+ 如果内存中多数对象都是存活的,这种算法将产生大量的复制开销;
+ 浪费内存空间,内存空间变为了原有的一半。
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_标记复制算法.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-f4572b93-f7f3-41cc-9901-93816e79c789.png)
基于新生代 “朝生夕灭” 的特点,大多数虚拟机都不会按照 1:1 的比例来进行内存划分,例如 HotSpot 虚拟机会将内存空间划分为一块较大的 `Eden` 和 两块较小的 `Survivor` 空间,它们之间的比例是 8:1:1 。 每次分配时只会使用 `Eden` 和其中的一块 `Survivor` ,发生垃圾回收时,只需要将存活的对象一次性复制到另外一块 `Survivor` 上,这样只有 10% 的内存空间会被浪费掉。当 `Survivor` 空间不足以容纳一次 `Minor GC` 时,此时由其他内存区域(通常是老年代)来进行分配担保。
......@@ -257,7 +257,7 @@ System.gc();
标记-整理算法是在标记完成后,让所有存活对象都向内存的一端移动,然后直接清理掉边界以外的内存。其优点在于可以避免内存空间碎片化的问题,也可以充分利用内存空间;其缺点在于根据所使用的收集器的不同,在移动存活对象时可能要全程暂停用户程序:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_标记移动算法.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-e674c49f-c55b-4eba-95ea-34be62d55a78.png)
## 五、经典垃圾收集器
......@@ -270,7 +270,7 @@ System.gc();
HotSpot 虚拟机中一共存在七款经典的垃圾收集器:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_hotspot_垃圾收集器.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-1fa20f99-d203-42d6-982c-f1bd66a0c929.png)
> 注:收集器之间存在连线,则代表它们可以搭配使用。
......@@ -278,14 +278,14 @@ HotSpot 虚拟机中一共存在七款经典的垃圾收集器:
Serial 收集器是最基础、历史最悠久的收集器,它是一个单线程收集器,在进行垃圾回收时,必须暂停其他所有的工作线程,直到收集结束,这是其主要缺点。它的优点在于单线程避免了多线程复杂的上下文切换,因此在单线程环境下收集效率非常高,由于这个优点,迄今为止,其仍然是 HotSpot 虚拟机在客户端模式下默认的新生代收集器:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_收集器1.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-ec6ec994-6fe4-4d5b-890c-7f31b5a607a0.png)
### 5.2 ParNew 收集器
他是 Serial 收集器的多线程版本,可以使用多条线程进行垃圾回收:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_收集器2.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-ae8c47c7-538b-426a-85e4-d422d1c37683.png)
### 5.3 Parallel Scavenge 收集器
......@@ -304,14 +304,14 @@ Parallel Scavenge 收集器提供两个参数用于精确控制吞吐量:
从名字也可以看出来,它是 Serial 收集器的老年代版本,同样是一个单线程收集器,采用 标记-整理 算法,主要用于给客户端模式下的 HotSpot 虚拟机使用:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_收集器1.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-b199ece2-8de2-4f24-b50a-ea0c0d16bd7b.png)
### 5.5 Paralled Old 收集器
Paralled Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,采用 标记-整理 算法实现:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_收集器3.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-30dd34d5-27df-4a6f-b391-5a7928dfb3ab.png)
### 5.6 CMS 收集器
......@@ -323,7 +323,7 @@ CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时
3. **重新标记 (remark)**:采用增量更新算法,对并发标记阶段因为用户线程运行而产生变动的那部分对象进行重新标记,耗时比初始标记稍长且需要暂停用户线程;
4. **并发清除 (inital sweep)**:并发清除掉已经死亡的对象,耗时长但不需要暂停用户线程。
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_收集器4.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-7ad8d755-53f2-422a-9cea-c792b0579d8b.png)
其优点在于耗时长的 并发标记 和 并发清除 阶段都不需要暂停用户线程,因此其停顿时间较短,其主要缺点如下:
......@@ -336,7 +336,7 @@ CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时
Garbage First(简称 G1)是一款面向服务端的垃圾收集器,也是 JDK 9 服务端模式下默认的垃圾收集器,它的诞生具有里程碑式的意义。G1 虽然也遵循分代收集理论,但不再以固定大小和固定数量来划分分代区域,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region)。每一个 Region 都可以根据不同的需求来扮演新生代的 `Eden` 空间、`Survivor` 空间或者老年代空间,收集器会根据其扮演角色的不同而采用不同的收集策略。
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/G1_内存布局.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-e0f5da26-6e46-4f9d-bfcc-0842cc7079e7.png)
上面还有一些 Region 使用 H 进行标注,它代表 Humongous,表示这些 Region 用于存储大对象(humongous object,H-obj),即大小大于等于 region 一半的对象。G1 收集器的运行大致可以分为以下四个步骤:
......@@ -346,7 +346,7 @@ Garbage First(简称 G1)是一款面向服务端的垃圾收集器,也是
3. **最终标记 (Final Marking)**:对用户线程做一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的少量的 STAB 记录。虽然并发标记阶段会处理 SATB 记录,但由于处理时用户线程依然是运行中的,因此依然会有少量的变动,所以需要最终标记来处理;
4. **筛选回收 (Live Data Counting and Evacuation)**:负责更新 Regin 统计数据,按照各个 Regin 的回收价值和成本进行排序,在根据用户期望的停顿时间进行来指定回收计划,可以选择任意多个 Regin 构成回收集。然后将回收集中 Regin 的存活对象复制到空的 Regin 中,再清理掉整个旧的 Regin 。此时因为涉及到存活对象的移动,所以需要暂停用户线程,并由多个收集线程并行执行。
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_收集器5.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-3cf7a78a-d541-49af-929a-4bf8f4f0edd9.png)
### 5.8 内存分配原则
......@@ -379,7 +379,7 @@ Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、卸载、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个部分统称为连接:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_类的生命周期.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-e154964a-9a0a-46de-bf37-75b483956d6c.png)
《Java 虚拟机规范》严格规定了有且只有六种情况必须立即对类进行初始化:
......@@ -458,7 +458,7 @@ Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数
JDK 9 之前的 Java 应用都是由这三种类加载器相互配合来完成加载:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_双亲委派模型.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-c1fcdc37-4e5a-4ed3-94b1-ad4afa2dba7c.png)
上图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”,“双亲委派模型” 要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器,需要注意的是这里的加载器之间的父子关系一般不是以继承关系来实现的,而是使用组合关系来复用父类加载器的代码。
......@@ -473,7 +473,7 @@ JDK 9 之后为了适应模块化的发展,类加载器做了如下变化:
+ 当平台及应用程序类加载器收到类加载请求时,要首先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载;
+ 启动类加载器、平台类加载器、应用程序类加载器全部继承自 `java.internal.loader.BuiltinClassLoader` ,BuiltinClassLoader 中实现了新的模块化架构下类如何从模块中加载的逻辑,以及模块中资源可访问性的处理。
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jvm_jdk9_双亲委派模型.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-04b18ddb-3457-4e46-ba53-78237d234e37.png)
## 七、程序编译
......@@ -506,7 +506,7 @@ JDK 9 之后为了适应模块化的发展,类加载器做了如下变化:
以上层次并不是固定不变的,根据不同的运行参数和版本,虚拟机可以调整分层的数量。各层次编译之间的交互转换关系如下图所示:
![](https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/分层编译的交互关系.png)
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/jvm/zongjie-2188c350-fdb8-4fee-b2f0-5311795f386b.png)
实施分层编译后,解释器、客户端编译器和服务端编译器就会同时工作,可以用客户端编译器获取更高的编译速度、用服务端编译器来获取更好的编译质量。
......@@ -570,4 +570,4 @@ public static String concat(String... strings) {
对于虚拟机执行子系统来说,每次数组元素的读写都带有一次隐含的上下文检查以避免访问越界。如果数组的访问发生在循环之中,并且使用循环变量来访问数据,即循环变量的取值永远在 [0,list.length) 之间,那么此时就可以消除整个循环的数据边界检查,从而避免多次无用的判断。
原文链接:https://github.com/heibaiying/Full-Stack-Notes
\ No newline at end of file
原文链接:https://github.com/heibaiying/Full-Stack-Notes
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册