Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

每个sql语句都会长期持有引用,加快FullGC频率 #1664

Closed
rocky-peng opened this issue Mar 31, 2017 · 15 comments
Closed

每个sql语句都会长期持有引用,加快FullGC频率 #1664

rocky-peng opened this issue Mar 31, 2017 · 15 comments

Comments

@rocky-peng
Copy link

rocky-peng commented Mar 31, 2017

发现fullgc频繁,执行dump操作,发现近45%的内存占用都是存储的sql语句(堆的设置是2G)。跟踪引用关系,找到了JdbcSqlStat类,接着找到JdbcDataSourceStat类,发现如下代码发现是在这持有了sql语句的引用:

public JdbcSqlStat createSqlStat(String sql) {
        lock.writeLock().lock();
        try {
            JdbcSqlStat sqlStat = sqlStatMap.get(sql);
            if (sqlStat == null) {
                sqlStat = new JdbcSqlStat(sql);
                sqlStat.setDbType(this.dbType);
                sqlStat.setName(this.name);
                sqlStatMap.put(sql, sqlStat);  //sqlStatMap是一个LinkedHashMap
            }

            return sqlStat;
        } finally {
            lock.writeLock().unlock();
        }
    }

进一步排查代码,暂时只发现三个方法对sqlStatMap对象存在移除操作:

1. com.alibaba.druid.stat.JdbcDataSourceStat#setMaxSqlSize
2. com.alibaba.druid.stat.JdbcDataSourceStat#reset
3. com.alibaba.druid.stat.JdbcDataSourceStat#getSqlStatMapAndReset

第二个reset方法,发现是通过前端指定的url来触发执行(我们这里的情况是没有调用这个url的)

第三个getSqlStatMapAndReset,发现是在通过注册了一个ServletContextListener来添加了一个定时任务,没隔300second来触发(因为我们这里的应用不是一个web所以也没有触发这个操作)

第一个setMaxSqlSize方法调用关系太多,也就没有一一梳理,不知道这个方法的调用策略是怎样的呢?

@fuyi-wang
Copy link

楼主这个问题最终是如何解决的啊

@rocky-peng
Copy link
Author

@fuyi-wang 我们当初开启了这个功能,但没有使用。找到原因后,就把这个功能给关闭了。线上就正常了。

如果需要使用这个功能的话,可以参考:https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98

@fuyi-wang
Copy link

@rocky-peng 我遇到的问题和你的一样,但又想使用 SQL监控的功能,我再查查看有没有其他的解决方案,多谢哈

@cqwzboy
Copy link

cqwzboy commented Apr 24, 2019

楼主,我是JdbcDataSourceStat类中属性
ConcurrentMap<Long, JdbcConnectionStat.Entry> connections = new ConcurrentHashMap
内存占用很大,跟你情况差不多,这个map占用内存超过总的一半,最终把StatFilter禁用掉才恢复正常的。

@raydl007
Copy link

一样的情况,springboot项目,运行一天就oom了

@rocky-peng
Copy link
Author

@xuexuegege 试试自己写工厂方法new出datasurce,new的时候应该有参数设置
image

@xuexuegege
Copy link

xuexuegege commented Mar 10, 2020

看了下,springboot集成druid是自动配置的,看了一下源码发现
com.alibaba.druid.spring.boot.autoconfigure.stat类中

    @Bean
    @ConfigurationProperties("spring.datasource.druid.filter.stat")
    @ConditionalOnProperty(
        prefix = "spring.datasource.druid.filter.stat",
        name = {"enabled"},
        matchIfMissing = true
    )
    @ConditionalOnMissingBean
    public StatFilter statFilter() {
        return new StatFilter();
    }
matchIfMissing = true

这里默认开启了,所以需要手动设置为false就行了
也就是

spring.datasource.druid.filter.stat.enabled=false

@xuexuegege
Copy link

xuexuegege commented May 13, 2020

其实有的业务需要监控,但是这里根据情况可以打开SQL合并,

String property = properties.getProperty("druid.stat.mergeSql");
if ("true".equals(property)) {
	this.mergeSql = true;
} else if ("false".equals(property)) {
	this.mergeSql = false;
}

这里SQL合并源码有点复杂,大致意思是简化sql语句,
比如:
select id from user where name = “xuemeng” -> select id from user where name = ?
update user set name = 'xuemeng' where id = 1 -> update user set name = ? where id = ?
然后因为这个SQL监控的LinkedHashMap<String, JdbcSqlStat> sqlStatMap 是以SQL语句作为键的,所以这里如果有很多类似结构的SQL语句执行,可以把SQL合并打开,就会产生很多相同的sql语句,而他的内部逻辑是如果有这个语句就不put了:

JdbcSqlStat sqlStat = (JdbcSqlStat)this.sqlStatMap.get(sql);
if (sqlStat == null) {
	sqlStat = new JdbcSqlStat(sql);
	sqlStat.setDbType(this.dbType);
	sqlStat.setName(this.name);
	this.sqlStatMap.put(sql, sqlStat);
}

这样就会大大减少这么map的大小。
当然如果有业务需求不能合并sql或者合并了sql也没有太大效果(结构重复的sql语句不多),也可以不设置sql合并而是设置druid.stat.sql.MaxSize该参数是控制这个map最大容量的

if (connectProperties != null) {
	Object arg = connectProperties.get("druid.stat.sql.MaxSize");
	if (arg == null) {
		arg = System.getProperty("druid.stat.sql.MaxSize");
	}

	if (arg != null) {
		try {
			this.maxSqlSize = Integer.parseInt(arg.toString());
		} catch (NumberFormatException var7) {
			LOG.error("maxSize parse error", var7);
		}
	}
}

因为这里重写了弃老规则,所以put的时候会判断下是否大于最大,是的话就会弃老

this.sqlStatMap = new LinkedHashMap<String, JdbcSqlStat>(16, 0.75F, false) {
	protected boolean removeEldestEntry(java.util.Map.Entry<String, JdbcSqlStat> eldest) {
		boolean remove = this.size() > JdbcDataSourceStat.this.maxSqlSize;
		if (remove) {
			JdbcSqlStat sqlStat = (JdbcSqlStat)eldest.getValue();
			if (sqlStat.getRunningCount() > 0L || sqlStat.getExecuteCount() > 0L) {
				JdbcDataSourceStat.this.skipSqlCount.incrementAndGet();
			}
		}

		return remove;
	}
};

@rocky-peng
Copy link
Author

@xuexuegege nice

@hurukawatinami
Copy link

nice, got it

@zfy68
Copy link

zfy68 commented Sep 1, 2021

顶一个

@zfy68
Copy link

zfy68 commented Sep 1, 2021

DruidDataSource dataSourceRDB = new DruidDataSource(); // 注掉下面的方法就可以了 dataSourceRDB.setFilters("stat")

@FamousCodee
Copy link

timeBetweenLogStatsMillis 设置这个属性可以解决

@AiClvGu
Copy link

AiClvGu commented Sep 8, 2023

mark一下

@lizongbo
Copy link
Collaborator

可以利用启动将缓存的条数调小,比如只保存3条
-Ddruid.stat.sql.MaxSize=3
或者开启druid.stat.mergeSql=true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants