Skip to content

Commit de35d10

Browse files
committed
modify jvm
1 parent 7af634d commit de35d10

25 files changed

+1817
-933
lines changed

ReadMe.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ todo
193193
* [深入理解JVM虚拟机11:Java内存异常原理与实践](docs/java/jvm/深入理解JVM虚拟机11:Java内存异常原理与实践.md)
194194
* [深入理解JVM虚拟机12:JVM性能管理神器VisualVM介绍与实战](docs/java/jvm/深入理解JVM虚拟机12:JVM性能管理神器VisualVM介绍与实战.md)
195195
* [深入理解JVM虚拟机13:再谈四种引用及GC实践](docs/java/jvm/深入理解JVM虚拟机13:再谈四种引用及GC实践.md)
196-
* [深入理解JVM虚拟机14:GC调优思路与常用工具](docs/java/jvm/深入理解JVM虚拟机:GC调优思路与常用工具.md)
196+
* [深入理解JVM虚拟机14:GC调优思路与常用工具](docs/java/jvm/深入理解JVM虚拟机14:GC调优思路与常用工具.md)
197197

198198
### Java网络编程
199199

docs/java/jvm/img.png

42.7 KB
Loading

docs/java/jvm/img_1.png

24.7 KB
Loading

docs/java/jvm/img_2.png

47.9 KB
Loading

docs/java/jvm/img_3.png

47.9 KB
Loading

docs/java/jvm/img_4.png

47.8 KB
Loading

docs/java/jvm/img_5.png

61.4 KB
Loading

docs/java/jvm/img_6.png

120 KB
Loading

docs/java/jvm/img_7.png

112 KB
Loading

docs/java/jvm/img_8.png

77.2 KB
Loading

docs/java/jvm/深入理解JVM虚拟机10:JVM常用参数以及调优实践.md

Lines changed: 492 additions & 246 deletions
Large diffs are not rendered by default.

docs/java/jvm/深入理解JVM虚拟机11:Java内存异常原理与实践.md

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
# Table of Contents
2-
1+
# 目录
32
* [实战内存溢出异常](#实战内存溢出异常)
43
* [1 . 对象的创建过程](#1--对象的创建过程)
54
* [2 . 对象的内存布局](#2--对象的内存布局)
@@ -77,16 +76,16 @@ Student stu =new Student("张三","18");
7776
* 实例数据
7877
* 对齐填充
7978

80-
**对象头:** 对象头中包含了对象运行时一些必要的信息,如GC分代信息,锁信息,哈希码,指向Class类元信息的指针等,其中对Javaer比较有用的是**锁信息与指向Class对象的指针**,关于锁信息,后期有机会讲解并发编程JUC时再扩展,关于指向Class对象的指针其实很好理解。比如上面那个Student的例子,当我们拿到stu对象时,调用Class stuClass=stu.getClass();的时候,其实就是根据这个指针去拿到了stu对象所属的Student类在方法区存放的Class类对象。虽然说的有点拗口,但这句话我反复琢磨了好几遍,应该是说清楚了。
79+
**对象头:**对象头中包含了对象运行时一些必要的信息,如GC分代信息,锁信息,哈希码,指向Class类元信息的指针等,其中对Javaer比较有用的是**锁信息与指向Class对象的指针**,关于锁信息,后期有机会讲解并发编程JUC时再扩展,关于指向Class对象的指针其实很好理解。比如上面那个Student的例子,当我们拿到stu对象时,调用Class stuClass=stu.getClass();的时候,其实就是根据这个指针去拿到了stu对象所属的Student类在方法区存放的Class类对象。虽然说的有点拗口,但这句话我反复琢磨了好几遍,应该是说清楚了。
8180

82-
**实例数据:** 实例数据部分是对象真正存储的有效信息,就是程序代码中所定义的各种类型的字段内容。
81+
**实例数据:**实例数据部分是对象真正存储的有效信息,就是程序代码中所定义的各种类型的字段内容。
8382

84-
**对齐填充:** 虚拟机规范要求对象大小必须是8字节的整数倍。对齐填充其实就是来补全对象大小的。
83+
**对齐填充:**虚拟机规范要求对象大小必须是8字节的整数倍。对齐填充其实就是来补全对象大小的。
8584

8685
## 3 . 对象的访问定位
8786

8887
谈到对象的访问,还拿上面学生的例子来说,当我们拿到stu对象时,直接调用stu.getName();时,其实就完成了对对象的访问。但这里要累赘说一下的是,stu虽然通常被认为是一个对象,其实准确来说是不准确的,stu只是一个变量,变量里存储的是指向对象的指针,(如果干过C或者C++的小伙伴应该比较清楚指针这个概念),当我们调用stu.getName()时,虚拟机会根据指针找到堆里面的对象然后拿到实例数据name.需要注意的是,当我们调用stu.getClass()时,虚拟机会首先根据stu指针定位到堆里面的对象,然后根据对象头里面存储的指向Class类元信息的指针再次到方法区拿到Class对象,进行了两次指针寻找。具体讲解图如下:
89-
![在这里插入图片描述](https://img-blog.csdnimg.cn/201901092235030.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE5MjczMg==,size_16,color_FFFFFF,t_70)
88+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223333.png)
9089

9190
## 4 .实战内存异常
9291

@@ -140,8 +139,10 @@ Dumping heap to /Users/zdy/Desktop/dump/java_pid1099.hprof …
140139
可以看到生成了dump文件到指定目录。并且爆出了OutOfMemoryError,还告诉了你是哪一片区域出的问题:heap space
141140

142141
打开VisualVM工具导入对应的heapDump文件(如何使用请读者自行查阅相关资料),相应的说明见图:
143-
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190109224456933.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE5MjczMg==,size_16,color_FFFFFF,t_70)
144-
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190109224531128.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE5MjczMg==,size_16,color_FFFFFF,t_70)
142+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223348.png)
143+
144+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223403.png)
145+
145146
分析dump文件后,我们可以知道,OOMObject这个类创建了810326个实例。所以它能不溢出吗?接下来就在代码里找这个类在哪new的。排查问题。(我们的样例代码就不用排查了,While循环太凶猛了)分析dump文件后,我们可以知道,OOMObject这个类创建了810326个实例。所以它能不溢出吗?接下来就在代码里找这个类在哪new的。排查问题。(我们的样例代码就不用排查了,While循环太凶猛了)
146147

147148
### Java栈内存异常
@@ -182,18 +183,18 @@ public class JavaVMStackSOF {
182183
可以看到,递归调用了751次,栈容量不够用了。
183184
默认的栈容量在正常的方法调用时,栈深度可以达到1000-2000深度,所以,一般的递归是可以承受的住的。如果你的代码出现了StackOverflowError,首先检查代码,而不是改参数。
184185

185-
这里顺带提一下,很多人在做多线程开发时,当创建很多线程时,**容易出现OOM(OutOfMemoryError),** 这时可以通过具体情况,减少最大堆容量,或者栈容量来解决问题,这是为什么呢。请看下面的公式:
186+
这里顺带提一下,很多人在做多线程开发时,当创建很多线程时,**容易出现OOM(OutOfMemoryError),**这时可以通过具体情况,减少最大堆容量,或者栈容量来解决问题,这是为什么呢。请看下面的公式:
186187

187188
<font color="red">线程数*(最大栈容量)+最大堆值+其他内存(忽略不计或者一般不改动)=机器最大内存</font>
188189

189190
当线程数比较多时,且无法通过业务上削减线程数,那么再不换机器的情况下,**你只能把最大栈容量设置小一点,或者把最大堆值设置小一点。**
190191

191192
### 方法区内存异常
192193

193-
写到这里时,作者本来想写一个无限创建动态代理对象的例子来演示方法区溢出,避开谈论JDK7与JDK8的内存区域变更的过渡,但细想一想,还是把这一块从始致终的说清楚。在上一篇文章中JVM系列之Java内存结构详解讲到方法区时提到,JDK7环境下方法区包括了(运行时常量池),其实这么说是不准确的。因为从JDK7开始,HotSpot团队就想到开始去"永久代",大家首先明确一个概念,**方法区和"永久代"(PermGen space)是两个概念,方法区是JVM虚拟机规范,任何虚拟机实现(J9等)都不能少这个区间,而"永久代"只是HotSpot对方法区的一个实现。** 为了把知识点列清楚,我还是才用列表的形式:
194+
写到这里时,作者本来想写一个无限创建动态代理对象的例子来演示方法区溢出,避开谈论JDK7与JDK8的内存区域变更的过渡,但细想一想,还是把这一块从始致终的说清楚。在上一篇文章中JVM系列之Java内存结构详解讲到方法区时提到,JDK7环境下方法区包括了(运行时常量池),其实这么说是不准确的。因为从JDK7开始,HotSpot团队就想到开始去"永久代",大家首先明确一个概念,**方法区和"永久代"(PermGen space)是两个概念,方法区是JVM虚拟机规范,任何虚拟机实现(J9等)都不能少这个区间,而"永久代"只是HotSpot对方法区的一个实现。**为了把知识点列清楚,我还是才用列表的形式:
194195

195-
* [ ]  JDK7之前(包括JDK7)拥有"永久代"(PermGen space),用来实现方法区。但在JDK7中已经逐渐在实现中把永久代中把很多东西移了出来,比如:符号引用(Symbols)转移到了native heap,运行时常量池(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap.
196-
* [ ]  所以这就是为什么我说上一篇文章中说方法区中包含运行时常量池是不正确的,因为已经移动到了java heap;
196+
* [ ] JDK7之前(包括JDK7)拥有"永久代"(PermGen space),用来实现方法区。但在JDK7中已经逐渐在实现中把永久代中把很多东西移了出来,比如:符号引用(Symbols)转移到了native heap,运行时常量池(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap.
197+
* [ ] 所以这就是为什么我说上一篇文章中说方法区中包含运行时常量池是不正确的,因为已经移动到了java heap;
197198
**在JDK7之前(包括7)可以通过-XX:PermSize -XX:MaxPermSize来控制永久代的大小.
198199
JDK8正式去除"永久代",换成Metaspace(元空间)作为JVM虚拟机规范中方法区的实现。**
199200
元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但仍可以通过参数控制:-XX:MetaspaceSize与-XX:MaxMetaspaceSize来控制大小。
@@ -316,7 +317,8 @@ Java的一个重要优点就是通过垃圾收集器(Garbage Collection,GC)自
316317
为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
317318

318319
以下,我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。
319-
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190110151433131.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE5MjczMg==,size_16,color_FFFFFF,t_70)
320+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223428.png)
321+
320322
Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。
321323

322324
## 什么是Java中的内存泄露?
@@ -326,7 +328,8 @@ Java使用有向图的方式进行内存管理,可以消除引用循环的问
326328
在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。
327329

328330
通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。
329-
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190110152226472.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzE5MjczMg==,size_16,color_FFFFFF,t_70)
331+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223441.png)
332+
330333
因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。
331334

332335
对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。
@@ -443,12 +446,6 @@ Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Appli
443446
当设置好所有的参数了,我们就可以在OptimizeIt环境下运行被测程序,在程序运行过程中,Optimizeit可以监视内存的使用曲线(如下图),包括JVM申请的堆(heap)的大小,和实际使用的内存大小。另外,在运行过程中,我们可以随时暂停程序的运行,甚至强行调用GC,让GC进行内存回收。通过内存使用曲线,我们可以整体了解程序使用内存的情况。这种监测对于长期运行的应用程序非常有必要,也很容易发现内存泄露。
444447

445448

446-
447-
448-
449-
450-
451-
452449
## 参考文章
453450

454451
<https://segmentfault.com/a/1190000009707894>

docs/java/jvm/深入理解JVM虚拟机12:JVM性能管理神器VisualVM介绍与实战.md

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
# Table of Contents
2-
1+
# 目录
32
* [一、VisualVM是什么?](#一、visualvm是什么?)
43
* [二、如何获取VisualVM?](#二、如何获取visualvm?)
54
* [三、获取那个版本?](#三、获取那个版本?)
@@ -13,7 +12,6 @@
1312
* [Java技术江湖](#java技术江湖)
1413
* [个人公众号:黄小斜](#个人公众号:黄小斜)
1514

16-
1715
本文转自互联网,侵删
1816

1917
本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看
@@ -36,9 +34,6 @@
3634

3735
## 一、VisualVM是什么?
3836
VisualVM是一款免费的JAVA虚拟机图形化监控分析工具。
39-
40-
41-
4237
1. 拥有图形化的监控界面。
4338
2. 提供本地、远程的JVM监控分析功能。
4439
3. 是一款免费的JAVA工具。
@@ -49,13 +44,7 @@
4944
VisualVM各版本下载页面: http://visualvm.java.net/releases.html
5045

5146
下载VisualVM时也应该注意,不同的JDK版本对应不同版本的VisualVM,具体根据安装的JDK版本来下载第一的VisualVM。
52-
53-
54-
55-
56-
5747
## 三、获取那个版本?
58-
5948

6049
下载版本参考:Java虚拟机性能管理神器 - VisualVM(4) - JDK版本与VisualVM版本对应关系
6150

docs/java/jvm/深入理解JVM虚拟机13:再谈四种引用及GC实践.md

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
# Table of Contents
2-
1+
# 目录
32
* [一、背景](#一、背景)
43
* [二、简介](#二、简介)
54
* [1.强引用 StrongReference](#1强引用-strongreference)
@@ -42,8 +41,8 @@ Java的内存回收不需要程序员负责,JVM会在必要时启动Java GC完
4241
### 1.强引用 StrongReference
4342

4443
StrongReference是Java的默认引用形式,使用时不需要显示定义。任何通过强引用所使用的对象不管系统资源有多紧张,Java GC都不会主动回收具有强引用的对象。
45-
46-
<pre>public class StrongReferenceTest {
44+
````
45+
public class StrongReferenceTest {
4746
4847
public static int M = 1024*1024;
4948
@@ -76,17 +75,16 @@ StrongReference是Java的默认引用形式,使用时不需要显示定义。
7675
System.out.println("strongReference : "+strongReference);
7776
}
7877
}
79-
</pre>
78+
````
8079

8180
运行结果:
8281

83-
![](https://user-gold-cdn.xitu.io/2018/1/7/160cd0dc536b2384?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
84-
82+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223852.png)
8583
### 2.弱引用 WeakReference
8684

8785
如果一个对象只具有弱引用,无论内存充足与否,Java GC后对象如果只有弱引用将会被自动回收。
88-
89-
<pre>public class WeakReferenceTest {
86+
````
87+
public class WeakReferenceTest {
9088
9189
public static int M = 1024*1024;
9290
@@ -110,17 +108,16 @@ StrongReference是Java的默认引用形式,使用时不需要显示定义。
110108
System.out.println("weakRerference.get() : "+weakRerference.get());
111109
}
112110
}
113-
</pre>
111+
````
114112

115113
运行结果:
116114

117-
![](https://user-gold-cdn.xitu.io/2018/1/7/160cd0f1ead8184e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
118-
115+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223905.png)
119116
### 3.软引用 SoftReference
120117

121118
软引用和弱引用的特性基本一致, 主要的区别在于软引用在内存不足时才会被回收。如果一个对象只具有软引用,Java GC在内存充足的时候不会回收它,内存不足时才会被回收。
122-
123-
<pre>public class SoftReferenceTest {
119+
````
120+
public class SoftReferenceTest {
124121
125122
public static int M = 1024*1024;
126123
@@ -151,17 +148,16 @@ StrongReference是Java的默认引用形式,使用时不需要显示定义。
151148
System.out.println("softRerference2.get() : "+softRerference2.get());
152149
}
153150
}
154-
</pre>
155151
152+
````
156153
运行结果:
157154

158-
![](https://user-gold-cdn.xitu.io/2018/1/7/160cd1023a8f3de2?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
159-
155+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223922.png)
160156
### 4.虚引用 PhantomReference
161157

162158
从PhantomReference类的源代码可以知道,它的get()方法无论何时返回的都只会是null。所以单独使用虚引用时,没有什么意义,需要和引用队列ReferenceQueue类联合使用。当执行Java GC时如果一个对象只有虚引用,就会把这个对象加入到与之关联的ReferenceQueue中。
163-
164-
<pre>public class PhantomReferenceTest {
159+
````
160+
public class PhantomReferenceTest {
165161
166162
public static int M = 1024*1024;
167163
@@ -205,12 +201,11 @@ StrongReference是Java的默认引用形式,使用时不需要显示定义。
205201
System.out.println("referenceQueue.poll() : "+referenceQueue.poll());
206202
}
207203
}
208-
</pre>
209204
205+
````
210206
运行结果:
211207

212-
![](https://user-gold-cdn.xitu.io/2018/1/7/160cd110cba23d84?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
213-
208+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230404223938.png)
214209
## 三、小结
215210

216211
强引用是 Java 的默认引用形式,使用时不需要显示定义,是我们平时最常使用到的引用方式。不管系统资源有多紧张,Java GC都不会主动回收具有强引用的对象。 弱引用和软引用一般在引用对象为非必需对象的时候使用。它们的区别是被弱引用关联的对象在垃圾回收时总是会被回收,被软引用关联的对象只有在内存不足时才会被回收。 虚引用的get()方法获取的永远是null,无法获取对象实例。Java GC会把虚引用的对象放到引用队列里面。可用来在对象被回收时做额外的一些资源清理或事物回滚等处理。 由于无法从虚引获取到引用对象的实例。它的使用情况比较特别,所以这里不把虚引用放入表格进行对比。这里对强引用、弱引用、软引用进行对比:

0 commit comments

Comments
 (0)