提交 c78a00d1 编写于 作者: S shuang.kou

update jvm部分文章图片地址更换

上级 40ffae9b
......@@ -264,7 +264,7 @@ JConsole 是基于 JMX 的可视化监视、管理工具。可以很方便的监
#### 连接 Jconsole
![连接 Jconsole](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/1JConsole连接.png)
![连接 Jconsole](./pictures/jdk监控和故障处理工具总结/1JConsole连接.png)
如果需要使用 JConsole 连接远程进程,可以在远程 Java 程序启动时加上下面这些参数:
......@@ -283,7 +283,7 @@ JConsole 是基于 JMX 的可视化监视、管理工具。可以很方便的监
#### 查看 Java 程序概况
![查看 Java 程序概况 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/2查看Java程序概况.png)
![查看 Java 程序概况 ](./pictures/jdk监控和故障处理工具总结/2查看Java程序概况.png)
#### 内存监控
......@@ -294,7 +294,7 @@ JConsole 可以显示当前内存的详细信息。不仅包括堆内存/非堆
> - **新生代 GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
> - **老年代 GC(Major GC/Full GC)**:指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
![内存监控 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/3内存监控.png)
![内存监控 ](./pictures/jdk监控和故障处理工具总结/3内存监控.png)
#### 线程监控
......@@ -302,7 +302,7 @@ JConsole 可以显示当前内存的详细信息。不仅包括堆内存/非堆
最下面有一个"检测死锁 (D)"按钮,点击这个按钮可以自动为你找到发生死锁的线程以及它们的详细信息 。
![线程监控 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/4线程监控.png)
![线程监控 ](./pictures/jdk监控和故障处理工具总结/4线程监控.png)
### Visual VM:多合一故障处理工具
......
......@@ -54,7 +54,7 @@
### 本文导火索
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/29176325.jpg)
![](./pictures/jvm垃圾回收/29176325.png)
当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
......@@ -66,7 +66,7 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(G
**堆空间的基本结构:**
![](https://imgkr.cn-bj.ufileos.com/01d330d8-2710-4fad-a91c-7bbbfaaefc0e.png)
![](./pictures/jvm垃圾回收/01d330d8-2710-4fad-a91c-7bbbfaaefc0e.png)
上图所示的 Eden 区、From Survivor0("From") 区、To Survivor1("To") 区都属于新生代,Old Memory 区属于老年代。
......@@ -97,7 +97,7 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(G
经过这次GC后,Eden区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到老年代中。
![堆内存常见分配策略 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/堆内存.jpg)
![堆内存常见分配策略 ](./pictures/jvm垃圾回收/堆内存.png)
### 1.1 对象优先在 eden 区分配
......@@ -125,10 +125,10 @@ public class GCTest {
}
```
通过以下方式运行:
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/25178350.jpg)
![](./pictures/jvm垃圾回收/25178350.png)
添加的参数:`-XX:+PrintGCDetails`
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/10317146.jpg)
![](./pictures/jvm垃圾回收/10317146.png)
运行结果 (红色字体描述有误,应该是对应于 JDK1.7 的永久代):
......@@ -207,7 +207,7 @@ public class GCTest {
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/11034259.jpg)
![](./pictures/jvm垃圾回收/11034259.png)
### 2.1 引用计数法
......@@ -236,7 +236,7 @@ public class ReferenceCountingGc {
这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
![可达性分析算法 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/72762049.jpg)
![可达性分析算法 ](./pictures/jvm垃圾回收/72762049.png)
### 2.3 再谈引用
......@@ -302,7 +302,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引
## 3 垃圾收集算法
![垃圾收集算法分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/垃圾收集算法.jpg)
![垃圾收集算法分类](./pictures/jvm垃圾回收/垃圾收集算法.png)
### 3.1 标记-清除算法
......@@ -311,18 +311,18 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引
1. **效率问题**
2. **空间问题(标记清除后会产生大量不连续的碎片)**
<img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/63707281.jpg" alt="公众号" width="500px">
![](./pictures/jvm垃圾回收/标记-清除算法.jpeg)
### 3.2 复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
<img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/90984624.jpg" alt="公众号" width="500px">
<img src="./pictures/jvm垃圾回收/90984624.png" alt="公众号" width="500px">
### 3.3 标记-整理算法
根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
![标记-整理算法 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/94057049.jpg)
![标记-整理算法 ](./pictures/jvm垃圾回收/94057049.png)
### 3.4 分代收集算法
......@@ -336,7 +336,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引
## 4 垃圾收集器
![垃圾收集器分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/垃圾收集器.jpg)
![垃圾收集器分类](./pictures/jvm垃圾回收/垃圾收集器.png)
**如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。**
......@@ -347,7 +347,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引
Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。
**新生代采用复制算法,老年代采用标记-整理算法。**
![ Serial 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/46873026.jpg)
![ Serial 收集器 ](./pictures/jvm垃圾回收/46873026.png)
虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
......@@ -357,7 +357,7 @@ Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收
**ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。**
**新生代采用复制算法,老年代采用标记-整理算法。**
![ParNew 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg)
![ParNew 收集器 ](./pictures/jvm垃圾回收/22018368.png)
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
......@@ -386,7 +386,7 @@ Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它
**Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。** Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在困难的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
**新生代采用复制算法,老年代采用标记-整理算法。**
![Parallel Scavenge 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg)
![Parallel Scavenge 收集器 ](./pictures/jvm垃圾回收/parllel-scavenge收集器.png)
### 4.4.Serial Old 收集器
......@@ -408,7 +408,7 @@ Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它
- **重新标记:** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- **并发清除:** 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
![CMS 垃圾收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/82825079.jpg)
![CMS 垃圾收集器 ](./pictures/jvm垃圾回收/CMS收集器.png)
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点:
......
......@@ -62,14 +62,12 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成
**JDK 1.8 之前:**
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png" width="600px"/>
</div>
![](./pictures/java内存区域/JVM运行时数据区域.png)
**JDK 1.8 :**
<div align="center">
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3Java运行时数据区域JDK1.8.png" width="600px"/>
</div>
![](./pictures/java内存区域/2019-3Java运行时数据区域JDK1.8.png)
**线程私有的:**
......@@ -93,7 +91,7 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成
1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
**注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。**
**注意:程序计数器是唯一一个不会出现 `OutOfMemoryError` 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。**
### 2.2 Java 虚拟机栈
......@@ -103,10 +101,10 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成
**局部变量表主要存放了编译期可知的各种数据类型**(boolean、byte、char、short、int、float、long、double)、**对象引用**(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
**Java 虚拟机栈会出现两种错误:StackOverFlowError 和 OutOfMemoryError。**
**Java 虚拟机栈会出现两种错误:`StackOverFlowError` 和 `OutOfMemoryError`。**
- **StackOverFlowError:** 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
- **OutOfMemoryError:** 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 错误。
- **`StackOverFlowError`:** 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
- **`OutOfMemoryError`:** 若 Java 虚拟机堆中没有空闲内存,并且垃圾回收器也无法提供更多内存的话。就会抛出 OutOfMemoryError 错误。
Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
......@@ -143,11 +141,11 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(G
2. 老生代(Old Generation)
3. 永生代(Permanent Generation)
![JVM堆内存结构-JDK7](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/JVM堆内存结构-JDK7.jpg)
![JVM堆内存结构-JDK7](./pictures/java内存区域/JVM堆内存结构-JDK7.png)
JDK 8 版本之后方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
![JVM堆内存结构-JDK8](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/JVM堆内存结构-jdk8.jpg)
![JVM堆内存结构-JDK8](./pictures/java内存区域/JVM堆内存结构-jdk8.png)
**上图所示的 Eden 区、两个 Survivor 区都属于新生代(为了区分,这两个 Survivor 区域按照顺序被命名为 from 和 to),中间一层属于老年代。**
......@@ -239,8 +237,7 @@ JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1
> 2. **JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代** 。
> 3. **JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)**
>
>
>
相关问题:JVM 常量池中存储的是对象还是引用呢?: https://www.zhihu.com/question/57109429/answer/151717241 by RednaxelaFX
......@@ -259,7 +256,7 @@ JDK1.4 中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**
### 3.1 对象的创建
下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。
![Java创建对象的过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Java创建对象的过程.png)
![Java创建对象的过程](./pictures/java内存区域/Java创建对象的过程.png)
#### Step1:类加载检查
......@@ -274,7 +271,7 @@ JDK1.4 中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的
![内存分配的两种方式](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/内存分配的两种方式.png)
![内存分配的两种方式](./pictures/java内存区域/内存分配的两种方式.png)
**内存分配并发问题(补充内容,需要掌握)**
......@@ -311,11 +308,11 @@ JDK1.4 中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**
1. **句柄:** 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
![对象的访问定位-使用句柄](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/对象的访问定位-使用句柄.png)
![对象的访问定位-使用句柄](./pictures/java内存区域/对象的访问定位-使用句柄.png)
2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。
![对象的访问定位-直接指针](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/对象的访问定位-直接指针.png)
![对象的访问定位-直接指针](./pictures/java内存区域/对象的访问定位-直接指针.png)
**这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。**
......@@ -343,7 +340,7 @@ System.out.println(str2==str3);//false
再给大家一个图应该更容易理解,图片来源:<https://www.journaldev.com/797/what-is-java-string-pool>
![String-Pool-Java](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3String-Pool-Java1-450x249.png)
![String-Pool-Java](./pictures/java内存区域/2019-3String-Pool-Java1-450x249.png)
**String 类型的常量池比较特殊。它的主要使用方法有两种:**
......@@ -371,7 +368,7 @@ System.out.println(str2==str3);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
```
![字符串拼接](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/字符串拼接-常量池2.png)
![字符串拼接](./pictures/java内存区域/字符串拼接-常量池2.png)
尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
### 4.2 String s1 = new String("abc");这句话创建了几个字符串对象?
......
......@@ -10,4 +10,4 @@
掌握JVM,是深入Java技术栈的必经之路。
![jv.png](https://i.loli.net/2019/09/10/HsJXU8S4oVtCTM7.png)
![jv.png](./pictures/HsJXU8S4oVtCTM7.png)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册