Skip to content

Commit d5b91de

Browse files
committed
Merge branch 'ups'
2 parents 4d21e6d + 061142e commit d5b91de

2 files changed

Lines changed: 102 additions & 2 deletions

File tree

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
[![issues](https://badgen.net/github/open-issues/doocs/source-code-hunter)](https://github.com/doocs/source-code-hunter/issues)
77
[![PRs Welcome](https://badgen.net/badge/PRs/welcome/green)](http://makeapullrequest.com)
88

9-
“技术深度” 与 “技术广度”是对开发者来说最为重要的两个维度,本项目致力于从源码层面,剖析和挖掘互联网行业主流技术的底层实现原理,**为广大开发者“提升技术深度”提供便利**
9+
“技术深度” 与 “技术广度”是对开发者来说最为重要的两个维度,本项目致力于从源码层面,剖析和挖掘互联网行业主流技术的底层实现原理,**为广大开发者 “提升技术深度” 提供便利**
1010

11-
加入我们,一起通读互联网行业主流框架及中间件源码,成为强大的“源码猎人”,目前开放的有 **Spring 全家桶****Mybatis 框架****Netty 框架****Dubbo 框架**,及 **Redis****Tomcat** 中间件等,让我们一起开拓新的领地,揭开这些源码的神秘面纱。本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析,希望能够使阅读源码变成一件简单有趣,且有价值的事情,抽空更新中...(如果本项目对您有帮助,请watch、star、fork 素质三连一波,鼓励一下作者,谢谢)
11+
加入我们,一起通读互联网行业主流框架及中间件源码,成为强大的 “源码猎人”,目前开放的有 **Spring 全家桶****Mybatis****Netty****Dubbo 框架**,及 **Redis****Tomcat** 中间件等,让我们一起开拓新的领地,揭开这些源码的神秘面纱。
12+
13+
本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析,希望能够使阅读源码变成一件简单有趣,且有价值的事情,抽空更新中... (如果本项目对您有帮助,请watch、star、fork 素质三连一波,鼓励一下作者,谢谢)
1214

1315
* Netlify: https://schunter.netlify.app
1416
* Gitee Pages: https://doocs.gitee.io/source-code-hunter
@@ -174,6 +176,7 @@
174176
* [Recycler对象池原理分析](docs/Netty/Netty技术细节源码分析/Recycler对象池原理分析.md)
175177
* [MpscLinkedQueue队列原理分析](docs/Netty/Netty技术细节源码分析/MpscLinkedQueue队列原理分析.md)
176178
* [HashedWheelTimer时间轮原理分析](docs/Netty/Netty技术细节源码分析/HashedWheelTimer时间轮原理分析.md)
179+
* [ByteBuf的内存泄漏原因与检测原理](docs/Netty/Netty技术细节源码分析/ByteBuf的内存泄漏原因与检测原理.md)
177180

178181
## Dubbo
179182

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
该文所涉及的netty源码版本为4.1.6。
2+
3+
## Netty中的ByteBuf为什么会发生内存泄漏
4+
在Netty中,ByetBuf并不是只采用可达性分析来对ByteBuf底层的byte[]数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。
5+
在每个ByteBuf中都维护着一个refCnt用来对ByteBuf的被引用数进行记录,当ByteBuf的retain()方法被调用时,将会增加refCnt的计数,而其release()方法被调用时将会减少其被引用数计数。
6+
```Java
7+
private boolean release0(int decrement) {
8+
for (;;) {
9+
int refCnt = this.refCnt;
10+
if (refCnt < decrement) {
11+
throw new IllegalReferenceCountException(refCnt, -decrement);
12+
}
13+
if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
14+
if (refCnt == decrement) {
15+
deallocate();
16+
return true;
17+
}
18+
return false;
19+
}
20+
}
21+
}
22+
```
23+
当调用了ByteBuf的release()方法的时候,最后在上方的release0()方法中将会为ByteBuf的引用计数减一,当引用计数归于0的时候,将会调用deallocate()方法对其对应的底层存储数组进行释放(在池化的ByteBuf中,在deallocate()方法里会把该ByteBuf的byte[]回收到底层内存池中,以确保byte[]可以重复利用)。
24+
由于Netty中的ByteBuf并不是随着申请之后会马上使其引用计数归0而进行释放,往往在这两个操作之间还有许多操作,如果在这其中如果发生异常抛出导致引用没有及时释放,在使用池化ByetBuffer的情况下内存泄漏的问题就会产生。
25+
当采用了池化的ByteBuffer的时候,比如PooledHeapByteBuf和PooledDirectByteBuf,其deallocate()方法一共主要分为两个步骤。
26+
```Java
27+
@Override
28+
protected final void deallocate() {
29+
if (handle >= 0) {
30+
final long handle = this.handle;
31+
this.handle = -1;
32+
memory = null;
33+
chunk.arena.free(chunk, handle, maxLength);
34+
recycle();
35+
}
36+
}
37+
```
38+
- 将其底层的byte[]通过free()方法回收到内存池中等待下一次使用。
39+
- 通过recycle()方法将其本身回收到对象池中等待下一次使用。
40+
关键在第一步的内存回收到池中,如果其引用计数未能在ByteBuf对象被回收之前归0,将会导致其底层占用byte[]无法回收到内存池PoolArena中,导致该部分无法被重复利用,下一次将会申请新的内存进行操作,从而产生内存泄漏。
41+
而非池化的ByteBuffer即使引用计数没有在对象被回收的时候被归0,因为其使用的是单独一块byte[]内存,因此也会随着java对象被回收使得底层byte[]被释放(由JDK的Cleaner来保证)。
42+
43+
## Netty进行内存泄漏检测的原理
44+
在Netty对于ByteBuf的检测中,一共包含4个级别。
45+
```Java
46+
if (level.ordinal() < Level.PARANOID.ordinal()) {
47+
if (leakCheckCnt ++ % samplingInterval == 0) {
48+
reportLeak(level);
49+
return new DefaultResourceLeak(obj);
50+
} else {
51+
return null;
52+
}
53+
}
54+
```
55+
以默认的SIMPLE级别为例,在这个级别下,Netty将会根据以ByteBuf创建的序列号与113进行取模来判断是否需要进行内存泄漏的检测追踪。当取模成功的时候,将会为这个ByteBuf产生一个对应的DefaultResourceLeak对象,DefaultResourceLeak是一个PhantomReference虚引用的子类,并有其对应的ReferenceQueue。之后通过SimpleLeakAwareByteBuf类来将被追踪的ByteBuf和DefaultResourceLeak包装起来。
56+
```Java
57+
@Override
58+
public boolean release(int decrement) {
59+
boolean deallocated = super.release(decrement);
60+
if (deallocated) {
61+
leak.close();
62+
}
63+
return deallocated;
64+
}
65+
```
66+
在包装类中,如果该ByteBuf成功deallocated释放掉了其持有的byte[]数组将会调用DefaultResourceLeak的close()方法来已通知当前ByteBuf已经释放了其持有的内存。
67+
正是这个虚引用使得该DefaultResourceLeak对象被回收的时候将会被放入到与这个虚引用所对应的ReferenceQueue中。
68+
```Java
69+
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
70+
if (ref == null) {
71+
break;
72+
}
73+
74+
ref.clear();
75+
76+
if (!ref.close()) {
77+
continue;
78+
}
79+
80+
String records = ref.toString();
81+
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
82+
if (records.isEmpty()) {
83+
logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
84+
"Enable advanced leak reporting to find out where the leak occurred. " +
85+
"To enable advanced leak reporting, " +
86+
"specify the JVM option '-D{}={}' or call {}.setLevel()",
87+
resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
88+
} else {
89+
logger.error(
90+
"LEAK: {}.release() was not called before it's garbage-collected.{}",
91+
resourceType, records);
92+
}
93+
}
94+
```
95+
Netty会在下一次ByteBuf的采样中通过reportLeak()方法将ReferenceQueue中的DefaultResourceLeak取出并判断其对应的ByteBuf是否已经在其回收前调用过其close()方法,如果没有,显然在池化ByteBuf的场景下内存泄漏已经产生,将会以ERROR日志的方式进行日志打印。
96+
97+
以上内容可以结合JVM堆外内存的资料进行阅读。

0 commit comments

Comments
 (0)