当前位置: 首页 > news >正文

Tomcat源码分析-关于tomcat热加载的一些思考

在前面的文章中,我们分析了 tomcat 类加载器的相关源码,也了解了 tomcat 支持类的热加载,意味着 tomcat 要涉及类的重复卸装/装载过程,这个过程是很敏感的,一旦处理不当,可能会引起内存泄露

卸载类

我们知道,class 信息存放在元数据区(1.7是 Perm 区),这一块的内存相比堆而言,只占据非常小的空间,但是如果处理不当,还是有可能会导致内存溢出。这让我回想起几年前的一个故障,线上环境启用了 tomcat 的自动 reload 功能,出现过 java.lang.OutOfMemoryError: PermGen space 问题,排查的结果是因为 tomcat 在自动重载应用的时候,没有正常卸载类,导致 Perm 区内存没能被释放而发生溢出。tomcat 会尽量避免这类问题的发生,但是不能百分之百保证不会出现,所以还是建议不要随意开启 reloadable 功能

卸载类的条件很苛刻,必须同时满足以下3点:
1、 该类所有的实例已经被回收
2、 加载该类的 ClassLoder 已经被回收
3、 该类对应的 java.lang.Class 对象没有任何地方被引用

针对第1点,保证所有的实例被回收,这点不难,tomcat 在 Context 组件中实例化这些对象,持有直接或间接的引用,所以在热部署的时候,只要回收 Context 组件即可保证实例对象被回收。

在前面的文章中我们分析了 tomcat 类加载器,tomcat 使用 ParallelWebappClassLoader 加载 Class,在热部署的时候自然也会回收该类加载器。但是要注意的是,ParallelWebappClassLoader 会作为线程上下文的类加载器,因此要避免该类加载器对象在其他地方被引用。其实,这个问题是最隐晦的,jdk 中有些类会持有线程上下文的类加载器,作为一个优秀的开源产品,tomcat 为我们解决了很多诸如此类的问题

此外,还要保证类对应的 java.lang.Class 对象没有任何地方引用,只要 Class 对象作用域限制在 Context 组件的作用范围便不会发生泄露,tomcat 也是这么做了,使用 Context 实现了隔离机制

热加载问题

热加载会面临很多问题,有很多坑,需要非常丰富的经验。下面针对 tomcat 中涉及的类加载器泄露、对象泄露、文件锁等这几类常见的问题加以分析讨论。如果您对热加载感兴趣的话,可以研究下阿里开源的 jarlinks

文件锁

在 Windows 系统下使用 URLConnection 读取本地 jar 包的资源时,它会将资源缓存起来,会导致该 jar 包资源被锁。如果这个时候使用 war 包进行重新部署,需要解压 war 包再把原来目录下面的 jar 包删除,由于 jar 包资源被锁,导致删除失败,重新部署自然也会失败。我们先来看一段代码,这段代码会抛出异常,java.nio.file.FileSystemException: E:\spring-boot-2.0.1.RELEASE.jar: 另一个程序正在使用此文件,进程无法访问,说明该 jar 包被锁了

String path = "E://spring-boot-2.0.1.RELEASE.jar";
File file = new File( path );
URL url = file.toURI().toURL();URLConnection uConn = url.openConnection();
uConn.getLastModified();    // 读取jar包信息

为了解决文件锁的问题,tomcat 禁用了 URLConnection 的缓存,是在 JreMemoryLeakPreventionListener 中完成的,关键代码如下所示:

// dummy.jar 不存在也没有关系
URL url = new URL("jar:file://dummy.jar!/");
URLConnection uConn = url.openConnection();

可能有些童鞋会有疑问,tomcat 只是针对该 URLConnection 对象禁用了缓存,而其它的 URLConnection 资源缓存未必被禁用啊。答案是肯定的,因为 URLConnection 的 defaultUseCaches 属性是静态变量

类加载器泄露

其中一种 JRE 内存泄露是因为上下文类加载器导致的内存泄露。某些 JRE 库以单例的形式存在,它的生命周期很长甚至会贯穿于整个 java 程序,它们会使用上下文类加载器加载类,并且保留了类加载器的引用,所以会导致被引用的类加载器无法被回收,而 tomcat 重加载 webapp 是创建一个新的类加载器来实现的,旧的类加载器无法被 gc 回收,致使其加载的 Class 也无法被回收,导致内存泄露。

DriverManager 就是典型的例子,它利用 jdk 提供的 SPI 机制加载 java.sql.Driver 驱动,而 jdk 提供的 SPI 机制便是使用上下文类加载器加载 Class 的,如果这类 jdbc 驱动由 ParallelWebappClassLoader 类加载器加载的话,就会导致该 ClassLoder 无法被回收,自然会出现内存泄露

我们来看看 tomcat 是怎么解决的?tomcat 是利用 LifecycleListener 处理 before_init 事件,将上下文类加载器置为系统类加载器,并且完成驱动的加载过程,最后,为了不影响其它的类加载,再将上下文类加载器重置为 ParallelWebappClassLoader

另外一种 JRE 内存泄露是因为当前线程会启动另外一个线程,这个时候新线程会引用当前线程的上下文类加载器,如果新线程无止尽地运行,那么上下文类加载器就会一直被引用,而无法被回收,导致内存泄露。sun.awt.AppContext.getAppContext() 便是典型的例子,它会在内部开启一个 AWT-AppKit 线程,直到图形化环境准备就绪,例如 ImageIO.getCacheDirectory()java.awt.Toolkit.getDefaultToolkit()

针对这种情况,解决思路也是一样的,只需要将当前上下文类加载器指定为系统类加载器即可,关键代码如下所示:

JreMemoryLeakPreventionListener.java@Override
public void lifecycleEvent(LifecycleEvent event) {if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {ClassLoader loader = Thread.currentThread().getContextClassLoader();try {// 当线程上下文类加载器指定为系统类加载器Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());if (driverManagerProtection) {DriverManager.getDrivers();}// 避免开启的子线程持有 ParallelWebappClassLoader 引用if (appContextProtection && !JreCompat.isJre8Available()) {ImageIO.getCacheDirectory();}if (awtThreadProtection && !JreCompat.isJre9Available()) {java.awt.Toolkit.getDefaultToolkit();}// 避免持有 ParallelWebappClassLoader 引用if (tokenPollerProtection && !JreCompat.isJre9Available()) {java.security.Security.getProviders();}// 忽略若干代码......} finally {// 再重置为 ParallelWebappClassLoader,避免影响其它的类的加载Thread.currentThread().setContextClassLoader(loader);}}

ThreadLocal 对象泄露

还有一种内存泄露是由于 ThreadLocal 引起的,假如我们在 ThreadLocal 中保存了对象A,而且对象A由 ParallelWebappClassLoader 加载,那么就可以看成线程引用了对象A。由于 tomcat 中处理请求的是线程池,意味着该线程会存活很长一段时间。webapp 热加载时,会重新实例化一个 ParallelWebappClassLoader 对象,如果线程未销毁,那么旧的 ParallelWebappClassLoader 也无法被回收,导致内存泄露。

解决 ThreadLocal 内存泄露最好的办法,自然是把线程池中的所有的线程销毁并重新创建。这个过程分为两步,第一步是将任务队列堵住,不让新的任务进来,第二步是将线程池中所有线程停止。

tomcat 解决该 ThreadLocal 对象泄露问题,也是借助了 Lifecycle 完成的,具体的实现类是 ThreadLocalLeakPreventionListener,它会处理 Lifecycle.AFTER_STOP_EVENT 事件,并且销毁线程池内的空闲线程,关键代码如下所示:

相关文章:

Tomcat源码分析-关于tomcat热加载的一些思考

在前面的文章中,我们分析了 tomcat 类加载器的相关源码,也了解了 tomcat 支持类的热加载,意味着 tomcat 要涉及类的重复卸装/装载过程,这个过程是很敏感的,一旦处理不当,可能会引起内存泄露 卸载类 我们知…...

DataWhale 大数据处理技术组队学习task4

五、分布式并行编程模型MapReduce 1. 概述 1.1 分布式并行编程 背景:摩尔定律已经开始逐渐失效,提升数据处理计算能力刻不容缓。传统的程序开发与分布式并行编程 传统的程序开发:以单指令、单数据流的方式顺序执行,虽然这种方式…...

Oracle 12C以上统计信息收集CDB、PDB执行时间不一致问题

文章目录前言一、统计信息窗口期调查二、时区调查三、查询alert记录四、why Database Statistic Collection Job is running two times inside a Maintenance Window?五、Default Scheduler Timezone Value In PDB$SEED Different Than CDB六、总结前言 在实际工作中发现一个…...

用Python获取弹幕的两种方式(一种简单但量少,另一量大管饱)

前言 弹幕可以给观众一种“实时互动”的错觉,虽然不同弹幕的发送时间有所区别,但是其只会在视频中特定的一个时间点出现,因此在相同时刻发送的弹幕基本上也具有相同的主题,在参与评论时就会有与其他观众同时评论的错觉。 在国内…...

算法训练营 day55 动态规划 买卖股票问题系列3

算法训练营 day55 动态规划 买卖股票问题系列3 最佳买卖股票时机含冷冻期 309. 最佳买卖股票时机含冷冻期 - 力扣(LeetCode) 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。 设计一个算法计算出最大利润。在满足以下…...

电商共享购模式,消费增值返利,app开发

在当今以市场需求为主导的数字经济时代,消费者需求呈现出精细化管理和多元化的特性,目标市场日渐完善,另外在大数据技术迅速进步和运用的驱动下,总体行业的发展节奏感也在不断加速。因而,企业需要建立一套灵活多变的经…...

机房信息牌系统

产品特色: 无线低功耗安装简单,快速布置易于维护墨水屏显示,清晰,更环保信息后台推送,远程管理多模版样式随意制作多尺寸:4.2寸,7.5寸,10.2寸4.2寸7.5寸10.2寸标签特性:…...

金测评 手感更细腻的游戏手柄,双模加持兼容更出色,雷柏V600S上手

很多朋友周末都喜欢玩玩游戏放松一下,在家玩游戏的时候,PC是大家常用的平台,当然了,玩游戏的时候用键鼠的话,手感难免差点意思,还是要手柄才能获得更好的体验。我现在用的是雷柏V600S,这是一款支…...

Windows10 下测试 Intel SGX 功能

文章目录参考文献系统要求一、安装Open Enclave SDK 环境(一)什么是Open Enclave SDK(二)启动SGX功能方法一: BIOS启动方法二:软件方式启动(三)安装必要环境(1&#xff0…...

Tina_Linux_功耗管理_开发指南

Tina Linux 功耗管理开发指南 1 概述 1.1 编写目的 简要介绍tina 平台功耗管理机制,为关注功耗的开发者,维护者和测试者提供使用和配置参考。 1.2 适用范围 表1-1: 适用产品列表产品名称内核版本休眠类型参与功耗管理的协处理器R328Linux-4.9NormalS…...

golang编译dll失败问题解决

执行go build -buildmodec-shared -o exportgo.dll exportgo.go报类似如下错误/usr/lib/gcc/x86_64-pc-msys/9.1.0/../../../../x86_64-pc-msys/bin/ld: 找不到 -lmingwex/usr/lib/gcc/x86_64-pc-msys/9.1.0/../../../../x86_64-pc-msys/bin/ld: 找不到 -lmingw32安装tdm gcc m…...

Convolutional Neural Networks for Sentence Classification

摘要 We report on a series of experiments with convolutional neural networks (CNN) trained on top of pre-trained word vectors for sentence-level classification tasks. We show that a simple CNN with little hyperparameter tuning and static vectors achieves e…...

基于SpringBoot的共享汽车管理系统

文末获取源码 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven3.3.9 浏…...

TCP三次握手

参考:4.1 TCP 三次握手与四次挥手面试题 | 小林coding TCP 头格式 我们先来看看 TCP 头的格式,标注颜色的表示与本文关联比较大的字段,其他字段不做详细阐述。 序列号:在建立连接时由计算机生成的随机数作为其初始值&#xff0c…...

未来土地利用模拟FLUS模型

未来土地利用模拟(FutureLand-Use Simulation, FLUS)模型1 模型简介1.1 基于ANN 的适宜性概率计算1.2 基于自适应惯性机制的元胞自动机1.3 模拟精度评价参考流域 径流变化是 自然因素和 人为因素共同作用的结果,其中人为因素最为直接的方式就…...

压力传感器MPX5700D/MPX5700GP/MPX5700AP产品概述、特征

MPX5700系列压阻式换能器是最先进的单片硅压力传感器,可广泛用于各种应用,特别是采用A/D输入微控制器或微处理器的应用。这一获得专利的单元件传感器集合了高级微加工技术、薄膜金属化、双极工艺,能够提供精确的、与所施加压力成正比的高电平…...

taobao.trades.sold.query( 根据收件人信息查询交易单号 )

¥开放平台免费API必须用户授权聚石塔内调用 根据收件人信息查询交易单号。 公共参数 请求地址: HTTP地址 公共请求参数: 公共响应参数: 请求参数 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey, secret); TradesSoldQueryRequest req new…...

【JavaWeb】JSON、AJAX(305-317)

305.JSON-什么是JSON JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON 采用完全独立于语言的文本格式,而且很多语言都提供了对 json 的支持(包括 C, C, C#, Java, JavaScript, Perl,…...

AI入场,搜索这个“营销枢纽”有新故事吗?

哪里有内容,哪里就有搜索。 以前,互联网离我们生活很远,传统搜索与用户的距离分割,只有当用户想要了解什么,才会去使用。 如今,互联网与真实世界密不可分,加之新技术、新平台的不断涌现&#xf…...

字节在职5年,一个测试工程师的坎坷之路

几年前进入到IT行业,现在发现学习软件测试的人越来越多,今天我想根据自己的行业经验给大家提一些建议。 跟其他行业相比,做软件测试的岗位确实算是高薪职业,我们那个时候起步的工资并不高,而看现在很多毕业的学生薪资都…...

什么是web框架?

什么是web框架? 我们解释一个概念的时候,通常会用到其他更多的概念去解释它,如果听的人不理解解释它的概念,那么这个解释是失败的,因此首先要回答一下解释web框架中所用到的概念。 回答这个问题前,首先需…...

说一说关系数据库中的范式建模

面试中可能会被问到,来回顾总结一下,参考《数据库系统第五版》(王珊/萨师煊) 范式(normal form),我的理解是用来规范关系数据库中实体如何划分以及实体间如何建立联系来保持数据完整性的一种指导思想,目的就…...

Mysql是怎样运行的之Inno页介绍

一、InnoDB介绍 InnoDB是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内…...

【华为OD机试模拟题】用 C++ 实现 - 找字符(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 货币单位换算(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 选座位(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 停车场最大距离(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 重组字符串(2023.Q1) 【华为OD机试模…...

JAVA 8 新特性 Lamdba表达式

Java8 新特性: 1、Lamdba表达式 2、函数式接口 3、方法引用和构造引用 4、Stream API 5、接口中的默认方法和静态方法 6、新时间日期API 7、Optional 8、其他特性 Java8 优势:速度快、代码更少(增加了新的语法 Lambda 表达式)、强…...

使用antlr实现一个简单的表达式解析

背景 之前在做游戏的过程中,我们经常需要解析一些公式,比如(对方攻击值-对方防御值)*2这种表达式,我们习惯于用代码写死公式,但是这种方式不够灵活,我们想要的是一种灵活的解析方式, 只需要策划输入一个任…...

2月24日作业

题目:通过操作Cortex-A7核,串口输入相应的命令,控制LED灯进行工作--->上传CSDN 1.例如在串口输入led1on,开饭led1灯点亮 2.例如在串口输入led1off,开饭led1灯熄灭 3.例如在串口输入led2on,开饭led2灯点亮 4.例如在串口输入led2off,开饭led…...

SpringBoot可以同时处理多少请求?

本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star ⭐⭐⭐⭐⭐转载请注明出处:https://blog.csdn.net/weixin_43461520/article/details/129207427 前言 前两天面试的时候,面试官问我:一个i…...

代码随想录【Day23】| 669. 修剪二叉搜索树、108. 将有序数组转换为二叉搜索树、538. 把二叉搜索树转换为累加树

669. 修剪二叉搜索树 题目链接 题目描述: 给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新…...

Wsl2 ubuntu 配置git 阿里云codeup

目录 创建一个跟你windows git使用相同的用户名,特别重要 配置git 用户名和邮箱 配置阿里云codeup 拉取仓库提示文件权限问题 给用户目录权限 配置项目文件别名 key_load_public: invalid format 怎么办? WSL ubuntu sshd: no hostkeys available -- exiting…...