HDFS FileSystem 导致的内存泄露

news2024/9/21 5:35:21

目录

一、问题描述

二、问题定位和源码分析


一、问题描述


ftp程序读取windows本地文件写入HDFS,5天左右程序 重启一次,怀疑是为OOM挂掉,马上想着就分析 GC日志了。

### 打印gc日志
/usr/java/jdk1.8.0_162/bin/java  \
  -Xmx1024m -Xms512m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 \
  -XX:-ResizePLAB -verbose:gc -XX:-PrintGCCause -XX:+PrintAdaptiveSizePolicy \
  -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/hadoop/datadir/windeploy/log/ETL/DS/gc.log-`date +'%Y%m%d%H%M'` \
  -classpath $jarPath com.winnerinf.dataplat.ftp.FtpUtilDS

程序分配内存 1024M ,从gc日志可以看出,old区域的占用大小一直从100M上升到了1G,后面频繁的fullGc,但是释放的空间并不多,最后程序由于内存空间不足导致OOM。

我们使用 jmap 命令将该进程的 JVM 快照导出来进行分析:

jmap -dump:live,format=b,file=dump.hprof PID

这个命令会在当前目录下生成一个dump.hrpof文件,这里是二进制的格式,你不能直接打开看的,其把这一时刻JVM堆内存里所有对象的快照放到文件里去了,供你后续去分析。

Memory Analyzer Mat下载地址:Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation

https://kangll.blog.csdn.net/article/details/130222759?spm=1001.2014.3001.5502https://kangll.blog.csdn.net/article/details/130222759?spm=1001.2014.3001.5502Mat 工具给出了两个问题:

问题一

"org.apache.hadoop.fs.FileSystem$Cache"”的一个实例文件系统被"sun.misc.Launcher$AppClassLoader @ 0xc04e9290"加载。占用208,987,664字节(20.36%)。内存在“"org.apache.hadoop.fs.FileSystem$Cache"”的一个实例中累积。文件系统被程序"sun.misc.Launcher$AppClassLoader @ 0xc04e9290"加载。

问题二

7,670个“org.apache.hadoop.conf.Configuration”实例。配置”,由“sun.misc”加载。"sun.misc.Launcher$AppClassLoader @ 0xc04e9290" 占用813,527,552字节(79.25%)。这些实例是从"java.util.HashMap$Node[]",的一个实例中引用的。由"<系统类加载器>"加载。


二、问题定位和源码分析


问题的源头在于 org.apache.hadoop.fs.FileSystem 这个类,程序运行了5天, conf 类就产生了几千个实例。这些实例虽然占用的大小不大,但是每产生一个FileSystem实例时,它都会维护一个Properties对象(Hashtable的子类),用来存储hadoop的那些配置信息。hadoop的配置有几百个很正常,因此一个FileSystem实例就会对应上百个的Hashtable$Entity实例,就会占用大量内存。

为什么会有如此多的FileSystem实例呢?

以下是我们获取FIleSystem的方式:

Configuration conf = new Configuration();
conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
conf.set("fs.file.impl", "org.apache.hadoop.fs.LocalFileSystem");
FileSystem fileSystem = FileSystem.get(conf);

FileSystem.get底层是有使用缓存的,因此我们在每次使用完并没有关闭fileSystem,只是httpfs服务关闭时才去关闭FileSystem。

/****************************************************************
* 
* </ol>
*****************************************************************/
@SuppressWarnings("DeprecatedIsStillUsed")
@InterfaceAudience.Public
@InterfaceStability.Stable
public abstract class FileSystem extends Configured
implements Closeable, DelegationTokenIssuer {
  public static final String FS_DEFAULT_NAME_KEY =
  CommonConfigurationKeys.FS_DEFAULT_NAME_KEY;
  public static final String DEFAULT_FS =
  CommonConfigurationKeys.FS_DEFAULT_NAME_DEFAULT;
  /**
   * 获取这个URI的方案和权限的文件系统
   *  如果配置有属性{@code "fs.$SCHEME.impl.disable。缓存"}设置为true,
   *  将创建一个新实例,并使用提供的URI和进行初始化配置,然后返回而不被缓存
   * 
   *  如果有一个缓存的FS实例匹配相同的URI,它将被退回
   *  否则:将创建一个新的FS实例,初始化配置和URI,缓存并返回给调用者。
   */
  public static FileSystem get(URI uri, Configuration conf) throws IOException {
        String scheme = uri.getScheme();
        String authority = uri.getAuthority();

        if (scheme == null && authority == null) {     // use default FS
      return get(conf);
    }

        if (scheme != null && authority == null) {     // no authority
      URI defaultUri = getDefaultUri(conf);
      if (scheme.equals(defaultUri.getScheme())    // if scheme matches default
            && defaultUri.getAuthority() != null) {  // & default has authority
          return get(defaultUri, conf);              // return default
        }
    }
        //  //如果cache被关闭了,每次都会创建一个新的FileSystem
        String disableCacheName = String.format("fs.%s.impl.disable.cache", scheme);
        if (conf.getBoolean(disableCacheName, false)) {
      LOGGER.debug("Bypassing cache to create filesystem {}", uri);
      return createFileSystem(uri, conf);
    }

        return CACHE.get(uri, conf);
     }

  /** Caching FileSystem objects. */
  static class Cache {
    // ....
    FileSystem get(URI uri, Configuration conf) throws IOException{
      // key 的问题 ,详细我们看下 Key 
      Key key = new Key(uri, conf);
      return getInternal(uri, conf, key);
    }
  }
     /**
     *如果键映射到实例,则获取FS实例,如果没有找到FS,则创建并初始化FS。
    * 如果这是映射中的第一个条目,并且JVM没有关闭,那么它会注册一个关闭钩子来关闭文件系统,并将这个FS添加到{@code toAutoClose}集合中,
     * 如果{@code " FS .automatic。Close "}在配置中设置(默认为true)。
     */
    private FileSystem getInternal(URI uri, Configuration conf, Key key)
        throws IOException{
      FileSystem fs;
      synchronized (this) {
        fs = map.get(key);
      }
      if (fs != null) {
        return fs;
      }

      fs = createFileSystem(uri, conf);
      final long timeout = conf.getTimeDuration(SERVICE_SHUTDOWN_TIMEOUT,
          SERVICE_SHUTDOWN_TIMEOUT_DEFAULT,
          ShutdownHookManager.TIME_UNIT_DEFAULT);
      synchronized (this) { // refetch the lock again
        FileSystem oldfs = map.get(key);
        if (oldfs != null) { // a file system is created while lock is releasing
          fs.close(); // close the new file system
          return oldfs;  // return the old file system
        }

        // now insert the new file system into the map
        if (map.isEmpty()
                && !ShutdownHookManager.get().isShutdownInProgress()) {
          ShutdownHookManager.get().addShutdownHook(clientFinalizer,
              SHUTDOWN_HOOK_PRIORITY, timeout,
              ShutdownHookManager.TIME_UNIT_DEFAULT);
        }
        fs.key = key;
        map.put(key, fs);
        if (conf.getBoolean(
            FS_AUTOMATIC_CLOSE_KEY, FS_AUTOMATIC_CLOSE_DEFAULT)) {
          toAutoClose.add(key);
        }
        return fs;
      }
    }

}

我们服务的fs.%s.impl.disable.cache并没有开启,因此肯定有使用cache。所以问题很可能是Cache的key判断有问题。

 /** FileSystem.Cache.Key */
    static class Key {
      final String scheme;
      final String authority;
      final UserGroupInformation ugi;
      final long unique;   // an artificial way to make a key unique

      Key(URI uri, Configuration conf) throws IOException {
        this(uri, conf, 0);
      }

      Key(URI uri, Configuration conf, long unique) throws IOException {
        scheme = uri.getScheme()==null ?
            "" : StringUtils.toLowerCase(uri.getScheme());
        authority = uri.getAuthority()==null ?
            "" : StringUtils.toLowerCase(uri.getAuthority());
        this.unique = unique;

        this.ugi = UserGroupInformation.getCurrentUser();
      }

      @Override
      public int hashCode() {
        return (scheme + authority).hashCode() + ugi.hashCode() + (int)unique;
      }

      static boolean isEqual(Object a, Object b) {
        return a == b || (a != null && a.equals(b));
      }

      @Override
      public boolean equals(Object obj) {
        if (obj == this) {
          return true;
        }
        if (obj instanceof Key) {
          Key that = (Key)obj;
          return isEqual(this.scheme, that.scheme)
                 && isEqual(this.authority, that.authority)
                 && isEqual(this.ugi, that.ugi)
                 && (this.unique == that.unique);
        }
        return false;
      }

      @Override
      public String toString() {
        return "("+ugi.toString() + ")@" + scheme + "://" + authority;
      }
    }

我们使用 Mat工具 看下对象对比 scheme ,authority,ugi 的 哈希值

查看外部应用对象

  • java.util.HashMap$Node @ 0xc6756200
  • java.util.HashMap$Node @ 0xca5f51f8

我们集群开启了kerberos配置,可以看到 UGI对象的 hashCode不一样。

我们来看下 UGI类 

  public static FileSystem get(final URI uri, final Configuration conf,
        final String user) throws IOException, InterruptedException {
    String ticketCachePath =
      conf.get(CommonConfigurationKeys.KERBEROS_TICKET_CACHE_PATH);
      //这里每次获取的ugi的hashcode都不一样
    UserGroupInformation ugi =
        UserGroupInformation.getBestUGI(ticketCachePath, user);
    return ugi.doAs(new PrivilegedExceptionAction<FileSystem>() {
      @Override
      public FileSystem run() throws IOException {
        return get(uri, conf);
      }
    });
  }
  // UserGroupInformation.getBestUGI
  public static UserGroupInformation getBestUGI(
      String ticketCachePath, String user) throws IOException {
    if (ticketCachePath != null) {
      return getUGIFromTicketCache(ticketCachePath, user);
    } else if (user == null) {
      return getCurrentUser();
    } else {
        //最终走到这里
      return createRemoteUser(user);
    }    
  }
  // UserGroupInformation.createRemoteUser
  public static UserGroupInformation createRemoteUser(String user, AuthMethod authMethod) 
  {
    if (user == null || user.isEmpty()) {
      throw new IllegalArgumentException("Null user");
    }
    Subject subject = new Subject();
    subject.getPrincipals().add(new User(user));
    UserGroupInformation result = new UserGroupInformation(subject);
    result.setAuthenticationMethod(authMethod);
    return result;
  }

虽然使用cache,但是由于Key的判断问题,所以基本每次请求都会生成一个新的实例,就会出现内存泄露的问题。

ugi对象的不同是由于我们获取FileSystem时指定了用户有关 ,我们开启了 Kereros 而且指定了用户,根据下图的 UGI 对象可以看出每次请求都会生成一个新的实例,就会出现内存泄露的问题。

最后我们再看下程序的DEBUG日志:

2022-12-22 09:49:35  INFO [main] (HDFS.java:384) - FileSystem is
2022-12-22 09:49:35 DEBUG [IPC Parameter Sending Thread #0] (Client.java:1122) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM sending #9 org.apache.hadoop.hdfs.protocol.ClientProtocol.getFileInfo
2022-12-22 09:49:35 DEBUG [IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM] (Client.java:1176) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM got value #9
2022-12-22 09:49:35 DEBUG [main] (ProtobufRpcEngine.java:249) - Call: getFileInfo took 1ms
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:254) - hadoop login
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:187) - hadoop login commit
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:199) - using kerberos user:winner_spark@WINNER.COM
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:221) - Using user: "winner_spark@WINNER.COM" with name winner_spark@WINNER.COM
2022-12-22 09:49:35 DEBUG [main] (UserGroupInformation.java:235) - User entry: "winner_spark@WINNER.COM"
2022-12-22 09:49:35  INFO [main] (UserGroupInformation.java:1009) - Login successful for user winner_spark@WINNER.COM using keytab file /etc/security/keytabs/winner_spark.keytab
2022-12-22 09:49:35 DEBUG [IPC Parameter Sending Thread #0] (Client.java:1122) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM sending #10 org.apache.hadoop.hdfs.protocol.ClientProtocol.getListing
2022-12-22 09:49:35 DEBUG [IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM] (Client.java:1176) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM got value #10
2022-12-22 09:49:35 DEBUG [main] (ProtobufRpcEngine.java:249) - Call: getListing took 15ms
2022-12-22 09:49:35 DEBUG [IPC Parameter Sending Thread #0] (Client.java:1122) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM sending #11 org.apache.hadoop.hdfs.protocol.ClientProtocol.getListing
2022-12-22 09:49:35 DEBUG [IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM] (Client.java:1176) - IPC Client (1861781750) connection to hdp103/192.168.2.152:8020 from winner_spark@WINNER.COM got value #11
2022-12-22 09:49:35 DEBUG [main] (ProtobufRpcEngine.java:249) - Call: getListing took 10ms
2022-12-22 09:49:35  INFO [main] (HDFS.java:279) - Get a list of files and sort them by access time!
2022-12-22 09:49:35  INFO [main] (FtpUtilDS.java:192) - ----------- not exist rename failure file ---------------

程序DEBUG 日志 也显示每次IPC 遍历HDFS的文件的时候kerberos 还会进行一次登录。虽然fileSystem使用cache,但是由于Key的判断问题,所以基本每次请求都会生成一个新的实例,就会出现内存泄露的问题。

可以得出如果指定了用户,每次都会构造一个新的Subject,因此计算出来的UserGroupInformation的hashcode不一样。这样也最终导致FileSystem的Cache不生效。

 基于以上的分析,我将过去的旧代码做稍微修改:

Configuration conf = null;
FileSystem fileSystem = null;

....
finally {
            if (fileSystem != null) {
                fileSystem.close();
                conf.clear();
            }
        }

我们再看 程序运行了 两个多月没有重启,查看 堆内存发现 在新生代大部分的对象被回收,而且老年代占用内存持续维持在  10% 以下,至此问题解决。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/448032.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Net2FTP搭建免费web文件管理器『打造个人网盘』

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 前言 文件传输可以说是互联网最主要的应用之一&#xff0c;特别是智能设备的大面积使用&#xff0c;无论是个人存储文件资料&#xff0c;还是商业文件流转&#xff0c…

老杨说运维 | 数智时代,运维一体化如何落地实践?

在IT运维的发展过程中&#xff0c;随着分布式架构的加速推进&#xff0c;云原生技术加入应用&#xff0c;运维工具相比过去呈现出了更高强度的进化态势&#xff0c;即从多个相对独立的软件向EA形态的一体化系统进化。本次樱花论坛正是基于这一新的变革点&#xff0c;邀请了行业…

(十二)rk3568 NPU 中部署自己训练的模型,(2)模型转换

对于rknn 模型部署,本人使用*.pt -> *.onnx -> *.rknn的方式。 一、首先是pt文件到onnx文件的转换。 onnx文件导出时,需要修改models/yolo.py文件中的后处理部分。 注意:在训练时不要修改yolo.py的这段代码,训练完成后使用export.py进行模型导出转换时一定要进行修…

RHCE第六次作业

目录 一、编写脚本for1.sh,使用for循环创建20账户&#xff0c;账户名前缀由用户从键盘输入&#xff0c;账户初始密码由用户输入&#xff0c;例如: test1、test2、test3、.....、 test10 1.创建脚本for1.sh 2.执行脚本并查看是否创建成功 二、编写脚本for2.sh,使用for循环,通过…

微积分:微分

目录 1.代数推导 2.几何推导 3.总结 1.代数推导 假设我们有一个正方形初始边长为X&#xff0c;这时面积S1x 然后正方形的边长增加△x&#xff0c;此时面积S2&#xff08;x△x&#xff09; 变化的面积大小是△s&#xff08;x△x&#xff09;- x2x△x&#xff08;△x&#x…

软件测试外包干了4年,感觉废了..

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

c++算法——枚举法

枚举概念 枚举法是通过计算机速度快的特点&#xff0c;对问题所有可能性进行枚举&#xff0c;从中找到答案&#xff0c;需要利用循环。 例题 1&#xff0c;简单数字谜 题目描述 在□内填上一个合适的相同的数字&#xff0c;使等式“□365283□8256”成立。 输入格式 无 输出…

5.2 构造数值积分公式的基本方法与有关概念

学习目标&#xff1a; 如果要学习构造数值积分公式的基本方法与有关概念&#xff0c;可以遵循以下步骤&#xff1a; 1.了解数值积分的基本概念和性质&#xff1a;包括积分的定义、积分的性质、数值积分的定义及其误差等。这可以通过课本或相关的学习资料来了解。 2.掌握构造…

ubuntu 安装vmware tool(优先安装最新ubantu,可以不安装vmware tools)

1在虚拟机种站到安装vmware-tools 然后重启虚拟机 2在磁盘中可以看到如下文件&#xff0c;将zip文件移动到桌面解压备用 3关闭虚拟机 找到编辑虚拟机设置 4点击左侧 CD/dvd(SATA) 如果是使用镜像文件&#xff0c;改成使用物理驱动器. 5 打开命令行 cd 桌面 &#xff08;如…

yara规则--构建yara规则库

零、快速构建yara规则库的方案 Yara官方预置的规则库&#xff0c;链接 https://github.com/Yara-Rules/rules ClamAV的特征码转换为yara规则&#xff0c;利用工具clamav_to_yara.py将clamav的特征码转换为yara规则 从yara-generator爬取别人上传的样本的规则 利用 yarGen工具 …

电容笔和触控笔有什么区别?2023平价好用的电容笔测评

无论是导电的材料&#xff0c;还是工作的原理&#xff0c;还是操作的方式&#xff0c;甚至是价格&#xff0c;电容笔都和一般的触控笔有着明显的区别。电容笔具有更小的笔尖&#xff0c;并且具有更好的耐磨性。而且现在科技进步很快&#xff0c;IPAD的市场也越来越大&#xff0…

【蓝桥杯省赛真题18】python阴影图形面积 青少年组蓝桥杯python编程省赛真题解析

目录 python阴影图形面积 一、题目要求 1、编程实现 2、输入输出

Linux-零拷贝及Java实现

RabbitMQ比RocketMQ、Kafka较慢点一点重要原因就是 零拷贝 什么是零拷贝&#xff1f; 零拷贝指的是在进行IO的时候减少或避免让CPU拷贝数据&#xff08;数据在IO缓冲区中进行拷贝&#xff09; 零拷贝的优点&#xff1a; 减少甚至完全避免不必要的CPU拷贝&#xff0c;从而让C…

paddlepaddle 的 CPU 和 GPU

想记录一下一个 bug 改了一上午改到最后发现并没有 bug 的 bug。 总结&#xff1a; 因为下午要跑很久&#xff0c;为了省 GPU 算力&#xff0c;我想上午先用 CPU 把数据处理部分跑出来&#xff08;感觉数据处理部分不像网络训练那样涉及太多计算&#xff0c;所以感觉用 CPU 就…

JavaWeb开发 —— MyBatis动态SQL

目录 一、XML映射文件 1. 介绍 2. MyBatisX插件 二、MyBatis动态SQL 1. if 2. foreach 3. sql & include 一、XML映射文件 1. 介绍 ① XML映射文件的名称与Mapper接口名称一致&#xff0c;并且将XML映射文件和Mapper接口放置在相同包下&#xff08;同包同名…

【Java EE】-网络编程(三) TCP/IP协议详解

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀冬瓜的主页&#x1f319; 专栏&#xff1a;【JavaEE】 主要内容&#xff1a;应用层HTTP协议、DNS域名解析系统、传输层UDP协议&#xff0c;TCP协议。TCP协议的工作机制&#xff1a;确认应答、超时重传、连接管理、滑动窗口…

【Linux】MySQL高可用之读写分离监控实践

一、Mycat-web安装配置 1、Mycat节点安装zookeeper&#xff08;在mycat实现了读写分离上安装&#xff09; ① 解压zookeeper压缩包 tar -zxvf zookeeper-3.4.14.tar.gz -C /opt/② cd到cnf目录下将文件复制 ③ cd到bin目录下启动 ./zkServer.sh start2、Mycat节点安装mycat-we…

跨境卖家不可错过的2023开斋节选品和营销技巧,轻松拓展海外市场

开斋节是穆斯林世界最重要的节日之一&#xff0c;同时也是跨境电商一个非常重要的销售节点。在这个节日期间&#xff0c;跨境卖家可以通过合适的选品和营销策略吸引更多的消费者&#xff0c;提高销售额。本文将探讨2023年跨境卖家在开斋节期间如何做好选品和营销。 一、选品 1…

MySQL到ClickHouse数据同步方案对比

ClickHouse 在执行分析查询时的速度优势很好的弥补了 MySQL 的不足&#xff0c;但是对于很多开发者和DBA来说&#xff0c;如何将MySQL稳定、高效、简单的同步到 ClickHouse 却很困难。本文对比了 NineData、MaterializeMySQL&#xff08;ClickHouse自带&#xff09;、Bifrost 三…

下一代听歌识曲技术——从信号处理到深度学习

音乐丰富我们的生活&#xff1b;音乐传达人类的情感&#xff1b;音乐表达人类的艺术。人类文明的进程中离不开音乐这个载体&#xff0c;音乐也离不开人类的真情创作。在听到好听却没听过的歌曲时&#xff0c;如何快速准确得到该歌曲的歌名成为当务之急。LiveVideoStackCon 2022…