我一直都有一个疑问,丰巢业务服务的生产环境jvm参数设置是禁止system.gc的,也就是开启设置:-XX:+DisableExplicitGC,但是生产环境却从来没有出现过堆外内存溢出的情况。说明一下,丰巢使用了阿里开源的dubbo,而dubbo底层通信默认情况下使用了3.2.5.Final版本的netty,而我们对于netty的常规认知里,netty一定是使用了堆外内存,并且堆外内存在禁止了system.gc这个函数调用的话,在服务没有主动回收分配的堆外内存的情况下,一定会出现堆外内存的泄露。带着这个问题,刚好前天晚上有些时间,研究了一下3.2.5版本的netty源码,又是在科兴科兴园等馒头妈妈时候,发现了秘密之所在,我只能说,科兴科学园真是我的宝地啊。
 
涉及到的netty类:NioWorker、HeapChannelBufferFactory、BigEndianHeapChannelBuffer、SocketReceiveBufferPool
 
核心的秘密在SocketReceiveBufferPool中
  1. final class SocketReceiveBufferPool {
  2.  
  3. private static final int POOL_SIZE = 8;
  4.  
  5. @SuppressWarnings("unchecked")
  6. private final SoftReference<ByteBuffer>[] pool = new SoftReference[POOL_SIZE];
  7.  
  8. SocketReceiveBufferPool() {
  9. super();
  10. }
  11.  
  12. final ByteBuffer acquire(int size) {
  13. final SoftReference<ByteBuffer>[] pool = this.pool;
  14. for (int i = 0; i < POOL_SIZE; i ++) {
  15. SoftReference<ByteBuffer> ref = pool[i];
  16. if (ref == null) {
  17. continue;
  18. }
  19.  
  20. ByteBuffer buf = ref.get();
  21. if (buf == null) {
  22. pool[i] = null;
  23. continue;
  24. }
  25.  
  26. if (buf.capacity() < size) {
  27. continue;
  28. }
  29.  
  30. pool[i] = null;
  31.  
  32. buf.clear();
  33. return buf;
  34. }
  35.  
  36. ByteBuffer buf = ByteBuffer.allocateDirect(normalizeCapacity(size));
  37. buf.clear();
  38. return buf;
  39. }
  40.  
  41. final void release(ByteBuffer buffer) {
  42. final SoftReference<ByteBuffer>[] pool = this.pool;
  43. for (int i = 0; i < POOL_SIZE; i ++) {
  44. SoftReference<ByteBuffer> ref = pool[i];
  45. if (ref == null || ref.get() == null) {
  46. pool[i] = new SoftReference<ByteBuffer>(buffer);
  47. return;
  48. }
  49. }
  50.  
  51. // pool is full - replace one
  52. final int capacity = buffer.capacity();
  53. for (int i = 0; i< POOL_SIZE; i ++) {
  54. SoftReference<ByteBuffer> ref = pool[i];
  55. ByteBuffer pooled = ref.get();
  56. if (pooled == null) {
  57. pool[i] = null;
  58. continue;
  59. }
  60.  
  61. if (pooled.capacity() < capacity) {
  62. pool[i] = new SoftReference<ByteBuffer>(buffer);
  63. return;
  64. }
  65. }
  66. }
  67.  
  68. private static final int normalizeCapacity(int capacity) {
  69. // Normalize to multiple of 1024
  70. int q = capacity >>> 10;
  71. int r = capacity & 1023;
  72. if (r != 0) {
  73. q ++;
  74. }
  75. return q << 10;
  76. }
  77. }
SocketReceiveBufferPool中维护了一个SoftReference<ByteBuffer>类型的数组,关于java的SoftReference,大家可以自行搜索。其实就是在此类中维护了一个directbuffer的内存池,此部分的内存是可以重复利用的。那么问题来了,如果我们把netty用于接收网络信息的directbuffer直接传给dubbo的业务代码,那么这个内存池的作用是什么呢,内存如何被release回内存池?带着这个疑问,继续分析调用了SocketReceiveBufferPool的NioWorker代码。
  1. private boolean read(SelectionKey k) {
  2. final SocketChannel ch = (SocketChannel) k.channel();
  3. final NioSocketChannel channel = (NioSocketChannel) k.attachment();
  4.  
  5. final ReceiveBufferSizePredictor predictor =
  6. channel.getConfig().getReceiveBufferSizePredictor();
  7. final int predictedRecvBufSize = predictor.nextReceiveBufferSize();
  8.  
  9. int ret = 0;
  10. int readBytes = 0;
  11. boolean failure = true;
  12.  
  13. ByteBuffer bb = recvBufferPool.acquire(predictedRecvBufSize);
  14. try {
  15. while ((ret = ch.read(bb)) > 0) {
  16. readBytes += ret;
  17. if (!bb.hasRemaining()) {
  18. break;
  19. }
  20. }
  21. failure = false;
  22. } catch (ClosedChannelException e) {
  23. // Can happen, and does not need a user attention.
  24. } catch (Throwable t) {
  25. fireExceptionCaught(channel, t);
  26. }
  27.  
  28. if (readBytes > 0) {
  29. bb.flip();
  30.  
  31. final ChannelBufferFactory bufferFactory =
  32. channel.getConfig().getBufferFactory();
  33. final ChannelBuffer buffer = bufferFactory.getBuffer(readBytes);
  34. buffer.setBytes(0, bb);
  35. buffer.writerIndex(readBytes);
  36. //if(buffer instanceof BigEndianHeapChannelBuffer){
  37. // logger2.info("buffer instanceof BigEndianHeapChannelBuffer.");
  38. //}
  39. recvBufferPool.release(bb);
  40.  
  41. // Update the predi||\\|||||
  42. predictor.previousReceiveBufferSize(readBytes);
  43.  
  44. // Fire the event.
  45. fireMessageReceived(channel, buffer);
  46. } else {
  47. recvBufferPool.release(bb);
  48. }
  49.  
  50. if (ret < 0 || failure) {
  51. k.cancel(); // Some JDK implementations run into an infinite loop without this.
  52. close(channel, succeededFuture(channel));
  53. return false;
  54. }
  55.  
  56. return true;
  57. }
在代码里发现了netty会再创造一个chanelbuffer对象,然后将directbuffer里的内容复制到chanelbuffer里面,而这个chanelbuffer对象实际上是一个堆内内存,然后netty会真对这块内存进行解码及返回给上层调用服务等,也就是说没有直接将directbuffer返回给dubbo服务,这样也就解释了,我们在提供dubbo服务的jvm里,禁止掉了system.gc的情况下,没有发生过堆外内存泄漏的原因。后面我会找时间详细的分析一下netty4和kafka使用directbuffer的情况。

由dubbo服务禁用system.gc而引起的思考的更多相关文章

  1. 实战Java虚拟机之四:提升性能,禁用System.gc() ?

    今天开始实战Java虚拟机之四:"禁用System.gc()". 总计有5个系列 实战Java虚拟机之一“堆溢出处理” 实战Java虚拟机之二“虚拟机的工作模式” 实战Java虚拟 ...

  2. Dubbo_创建Dubbo服务并在ZooKeeper注册,然后通过Jar包执行

    一.安装ZooKeeper(略) 二.创建Dubbo服务  1.DemoService 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...

  3. RPC -dubbo 服务导出实现

    在阅读此文章之前,我希望阅读者对Spring 扩展机制的有一定的了解,比如:自定义标签与Spring整合, InitializingBean 接口,ApplicationContextAware,Be ...

  4. Dubbo 服务治理-mock实例

    转: Dubbo 服务治理-mock实例 老生住长亭 2017.02.28 10:56* 字数 514 阅读 2552评论 10喜欢 2 Dubbo的mock自己折腾的实例,配置信息有点简陋,有点粗鄙 ...

  5. dubbo系列四、dubbo服务暴露过程源码解析

    一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...

  6. (五)消费Dubbo服务

    前面我们搞了发布Dubbo服务,发布的服务就是用来消费的,所以我们这里来调用服务,消费下: 创建maven项目 dubbo-demo-consumer pom.xml配置下: <dependen ...

  7. (三)发布Dubbo服务

    我们现在来学习下发布Dubbo服务,主要参考dubbo开发包里的demo源码:由浅入深的讲解下这个小demo: github地址:https://github.com/apache/incubator ...

  8. 关于dubbo服务的xml配置文件报错的问题

    在配置dubbo服务的过程中,经常会遇到虽然程序能够跑起来,但是配置文件一堆红叉,虽然不影响功能,但是确实很让人恶心. 报错信息如下: Multiple annotations found at th ...

  9. Dubbo学习笔记2:Dubbo服务提供端与消费端应用的搭建

    Demo结构介绍 Demo使用Maven聚合功能,里面有三个模块,目录如下: 其中Consumer模块为服务消费者,里面TestConsumer和consumer.xml组成了基于Spring配置方式 ...

随机推荐

  1. Mysql 查询条件中字符串尾部有空格也能匹配上的问题

    一.表结构 TABLE person id name 1 你 2 你(一个空格) 3 你(二个空格) 二.查询与结果 select * from person where `name` = ? 无论 ...

  2. Beta 冲刺day3

    1.昨天的困难,今天解决的进度,以及明天要做的事情 昨天的困难:昨天主要是对第三方与企业复杂的逻辑关系进行分析和优化,以及进行部分模块的功能测试和代码测试. 今天解决的进度:根据前天得到的需求问题进行 ...

  3. spring中配置quartz调用两次及项目日志log4j不能每天生成日志解决方法

    在quartz中配置了一个方法运行时会连续调用两次,是因为加载两次,只需在tomcat的server.xml中修改配置 <Host name="www.xx.cn" appB ...

  4. QT https post请求(QNetworkRequest要设置SSL证书,而SSL证书认证有三种)

    因为https访问需要用到SSL认证,而QT默认是不支持SSL认证,所以在使用之前必须先做一些准备工作: 需要安装OpenSSL库: 1.首先打开http://slproweb.com/product ...

  5. 一些遇到的Qt程序在Windows平台间移植问题整理

    今天尝试把Qt程序移植到各种虚拟机中测试,由于Qt的依赖库报告往往不能显示出全部依赖库.结果频频出现问题,好不容易全部解决了,这里给出一些套路. 首先对于Qt版本,我用过很多,最终表示现阶段推荐Min ...

  6. CSS(选择器)

    CSS(选择器) 作用:用于匹配 HTML 元素 选择器分类: 1.元素选择器  a{} 2.伪元素选择器  ::before{}  (真实存在的元素) 3.类选择器   .link{} 4.属性选择 ...

  7. EF Core Model更新迁移

    EF Core 迁移 感觉就是以前EF Code First的自动同步数据库功能 内容:在你新增.更新TableModel后,如何自动化的更新DB中的真实Table.以及对这些更改进行一个版本控制. ...

  8. 免费Git客户端:sourcetree详细介绍

    一.简介:一个用于Windows和Mac的免费Git客户端.Sourcetree简化了如何与Git存储库进行交互,这样您就可以集中精力编写代码.通过Sourcetree的简单Git GUI可视化和管理 ...

  9. ubuntu 命令整合2

    通配符 * 匹配任意多个字符 ?匹配一个任意字符 示例:ls *.txt  rm -rf *.txt 文本编辑器 vi.vim 格式:vi 文件名 编辑 vi的三种工作模式 正常模式(启动进入的模式) ...

  10. Java 使用BigDecimal类处理高精度计算

    Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算.双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的 ...