踩坑JVM溢出-----mybatis一二级缓存

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/skymouse2002/article/details/82743906

根据需求开发了生产者消费者功能,生产者从Oracle数据库读取数据后,通过kafka发送给消费者,消费者从kafka中读取数据后,写入到mysql数据库中,功能需求就这样,比较简单,总量5000W数据,通过3个生产者读取写入到kafka中。

代码写完后经过简单的功能测试没问题,开始在测试环境上运行,大概运行了不到1小时发送了不到200万的数据时出现OOM   GC overhead limit exceeded,重复运行多次后确认存在问题,每次发送大概150W-200W数据时候出现OOM异常。

接下来在本地使用Jprofiler进行代码测试,发现运行一段时间后有大量的char[] 和String对象没有释放,如图:

而且size还在不断增大,这是导致OOM的直接原因,那么问题来了,是什么导致了这么多的char[] 和String对象呢?

首先排查代码,

		String topic="testCsrk1";	
		String clientId="CsrkProducer0";
		int begin=630681;//初始位置  630681    28000000
		int end = 28000000;//结束位置70987838   204138956
		int count=1 ;//循环控制条件
		int posi=100;//一次循环数量
		int id=begin;
		OracleProducer producer=new OracleProducer(topic,false,clientId);
		SqlSession sqlSession=factory.openSession(false);
		OCsrkMapper oCsrkMapper = sqlSession.getMapper(OCsrkMapper.class);
		
		int num=0;
		while(count>0) {			
			Map<String,Object> paramMap=new HashMap<String,Object>();
			paramMap.put("id", id);
			paramMap.put("posi", posi);
			paramMap.put("endid", id+posi);
			List<Csrk> list=oCsrkMapper.selectCsrks(paramMap);//从数据库查询数据
			int size=list.size();
			num+=size;
			logger.info("*******此次条数 "+size);
			System.out.println("*******总计条数 "+num);
			if(id>=end) {  //判断id是否超过发送的范围
				System.out.println(" id is " +id);
				count=0;
			}
			if(size==0) {
				id=id+posi;
			}
			for(int i=0;i<size;i++) {
				Csrk csrk=list.get(i);
				if(csrk!=null) {
					producer.send(csrk);//封装的kafka生产者发送数据
				}	
				id=list.get(i).getID();
			}
		}
		producer.close();
		System.out.println("producer closed");

没有使用char[]这种类型,代码中String对象也都是正常的,还得从其它方向上寻找问题。

这种类型的对象还有一种可能就是缓存,mybatis分为一级缓存和二级缓存。

一级缓存的作用域是SqlSession范围的,当在同一个sqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。
需要注意的是,如果SqlSession执行了DML操作(增删改),并且提交到数据库,MyBatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存中存储的是最新的信息,避免出现脏读现象。
当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。

二级缓存的作用域是mapper的同一个namespace。不同的sqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存,第二次查询会从缓存中获取数据,不再去底层数据库查询,从而提高效率。也就是说二级缓存是跨SqlSession进行数据共享的

注意:一级缓存是不能被关闭的

好了,回到代码中sqlSession只被创建了一次,因此一级缓存被创建了后并未被清楚,加入以下清除一级缓存的代码进行修改

			sqlSession.clearCache();
			List<Csrk> list=oCsrkMapper.selectCsrks(paramMap);//从数据库查询数据

重新使用Jprofiler进行测试,观察发现char[]和String对象的size仍然在增长,那代开mybatis的xml文件发现开启了二级缓存

<cache/>

那关闭对应sql的二级缓存

useCache="false"

重新使用Jprofiler执行,观察,发现char[]和String对象的size稳定在10M以下,连续运行超过2200万数据用时不到90分钟,内存使用量稳定在80M以下

 

后续的思考

1.如果xml中没有使用</cache>标签会不会不会出现这个问题

  在xml中去掉</cache>标签,同时去掉useCache="false"然后执行,观察char[]和String对象的size,会随着时间的增长而增长。

而在每次查询前加入sqlSession.clearCache();代码用来清除之前的一级缓存,观察观察char[]和String对象的size,很稳定,不会随着时间的增长而变化,说明清除一级缓存成功。

 

部分内容参考https://www.jianshu.com/p/c553169c5921

 

 

 

展开阅读全文

没有更多推荐了,返回首页