使用jmap和MAT分析JVM堆内存

我的一台生产环境机器每次运行几天之后就会莫名其妙的宕机,分析日志之后发现在tomcat刚启动的时候内存占用比较少,但是运行个几天之后内存占用越来越大,通过jmap命令可以查询到一些大对象引用没有被及时GC,这里就要求解决内存泄露的问题。

Java的内存泄露多半是因为对象存在无效的引用,对象得不到释放,如果发现Java应用程序占用的内存出现了泄露的迹象,那么我们一般采用下面的步骤分析:

  1. 用工具生成java应用程序的heap dump(如jmap)
  2. 使用Java heap分析工具(如MAT),找出内存占用超出预期的嫌疑对象
  3. 根据情况,分析嫌疑对象和其他对象的引用关系。
  4. 分析程序的源代码,找出嫌疑对象数量过多的原因。

以下一步步的按照项目实例来操作,去解决内存泄露的问题。

  1. 登录linux服务器,获取tomcat的pid,命令:
ps -ef|grep java
  1. 利用jmap初步分析内存映射,命令:
jmap -histo:live 3514 | head -7

在这里插入图片描述

第2行是我们业务系统的对象,通过这个对象的引用可以初步分析出到底是哪里出现了引用未被垃圾回收收集,通知开发人员优化相关代码。

  1. 如果上面一步还无法定位到关键信息,那么需要拿到heap dump,生成离线文件,做进一步分析,命令:
jmap -dump:live,format=b,file=heap.hprof 3514
  1. 拿到heap dump文件,利用eclipse插件MAT来分析heap profile。

a. 安装MAT插件

b. 在eclipse里切换到Memory Analysis视图

c. 用MAT打开heap profile文件。
在这里插入图片描述

直接看到下面Action窗口,有4种Action来分析heap profile,介绍其中最常用的2种:

  • Histogram:这个使用的最多,跟上面的jmap -histo 命令类似,只是在MAT里面可以用GUI来展示应用系统各个类产生的实例。

在这里插入图片描述

Shllow Heap排序后发现 Cms_Organization 这个类占用的内存比较多(没有得到及时GC),查看引用:
在这里插入图片描述

分析引用栈,找到无效引用,打开源码:

在这里插入图片描述

有问题的源码如下:

public class RefreshCmsOrganizationStruts implements Runnable{
 
	private final static Logger logger = Logger.getLogger(RefreshCmsOrganizationStruts.class);
	
	private List<Cms_Organization> organizations;
 
	private OrganizationDao organizationDao = (OrganizationDao) WebContentBean
			.getInstance().getBean("organizationDao");
	public RefreshCmsOrganizationStruts(List<Cms_Organization> organizations) {
		this.organizations = organizations;
	}
 
	public void run() {
		Iterator<Cms_Organization> iter = organizations.iterator();
		Cms_Organization organization = null;
		while (iter.hasNext()) {
			organization = iter.next();
			synchronized (organization) {
				try {
					organizationDao.refreshCmsOrganizationStrutsInfo(organization.getOrgaId());
					organizationDao.refreshCmsOrganizationResourceInfo(organization.getOrgaId());
					organizationDao.sleep();
				} catch (Exception e) {
					logger.debug("RefreshCmsOrganizationStruts organization = " + organization.getOrgaId(), e);
				}
			}
		}
	}
 
}

分析源码,定时任务定时调用,每次调用生成10个线程处理,而它又使用了非线程安全的List对象,导致List对象无法被GC收集,所以这里将List替换为CopyOnWriteArrayList 。

  • Dominator Tree:这个使用的也比较多,显示大对象的占用率。

在这里插入图片描述

同样的打开源码:

public class CategoryCacheJob extends QuartzJobBean implements StatefulJob {
	
	private static final Logger LOGGER = Logger.getLogger(CategoryCacheJob.class);
	
	public static Map<String,List<Cms_Category>> cacheMap = new java.util.HashMap<String,List<Cms_Category>>();
 
	@Override
	protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
		try {
			//LOGGER.info("======= 缓存编目树开始 =======");
			MongoBaseDao mongoBaseDao = (MongoBaseDao) BeanLocator.getInstance().getBean("mongoBaseDao");
			MongoOperations mongoOperations = mongoBaseDao.getMongoOperations();
			
			/*
			LOGGER.info("1.缓存基础教育编目树");
			Query query = Query.query(Criteria.where("isDel").is("0").and("categoryType").is("F"));
			query.sort().on("orderNo", Order.ASCENDING);
			List<Cms_Category> list = mongoOperations.find(query, Cms_Category.class);
			String key = query.toString().replaceAll("\\{|\\}|\\p{Cntrl}|\\p{Space}", "");
			key += "_CategoryCacheJob";
			cacheMap.put(key, list);
			*/
			
			//LOGGER.info("2.缓存职业教育编目树");
			Query query2 = Query.query(Criteria.where("isDel").is("0").and("categoryType").in("JMP","JHP"));
			query2.sort().on("orderNo", Order.ASCENDING);
			List<Cms_Category> list2 = mongoOperations.find(query2, Cms_Category.class);
			String key2 = query2.toString().replaceAll("\\{|\\}|\\p{Cntrl}|\\p{Space}", "");
			key2 += "_CategoryCacheJob";
			cacheMap.put(key2, list2);
			
			//LOGGER.info("3.缓存专题教育编目树");
			Query query3 = Query.query(Criteria.where("isDel").is("0").and("categoryType").is("JS"));
			query3.sort().on("orderNo", Order.ASCENDING);
			List<Cms_Category> list3 = mongoOperations.find(query3, Cms_Category.class);
			String key3 = query3.toString().replaceAll("\\{|\\}|\\p{Cntrl}|\\p{Space}", "");
			key3 += "_CategoryCacheJob";
			cacheMap.put(key3, list3);
			
			//LOGGER.info("======= 缓存编目树结束 =======");
		} catch(Exception ex) {
			LOGGER.error(ex.getMessage(), ex);
			LOGGER.info("======= 缓存编目树出错 =======");
		}
	}
 
}

这里的HashMap也有问题:居然使用定时任务,在容器启动之后定时将数据放到Map里面做缓存?这里修改这部分代码,替换为使用memcached缓存即可。

内存泄漏的原因分析,总结出来只有一条:存在无效的引用!良好的编码规范以及合理使用设计模式有助于解决此类问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM内存溢出时,我们需要进行以下步骤进行排查: 1. 确认是否是内存溢出导致程序崩溃,可以通过查看日志或者异常信息进行确认。 2. 确认内存溢出的具体原因,可以通过jmap、jstat等命令或者工具进行查看。其中,jmap命令可以导出当前JVM内存的状态信息,包括堆内存使用情况、对象数量、对象大小等;jstat命令可以实时监控JVM内存使用情况。 3. 分析内存溢出的具体原因,可以通过使用jhat、MAT等工具进行内存分析,查找内存泄漏或者过多的对象等问题。 下面是一些常用的命令: 1. jmap命令 jmap命令可以使用以下命令查看当前JVM内存使用情况: ``` jmap -heap [pid] ``` 其中,pid表示Java进程的进程号。该命令会输出JVM内存使用情况,包括堆内存大小、使用大小、最大值等。 2. jstat命令 jstat命令可以实时监控JVM内存使用情况,可以使用以下命令: ``` jstat -gcutil [pid] [interval] [count] ``` 其中,interval表示监控间隔时间,count表示监控次数。该命令会输出JVM内存使用情况,包括Eden区、Survivor区、Old区的使用情况等。 3. jhat命令 jhat命令可以使用以下命令生成堆转储文件: ``` jmap -dump:file=[filename] [pid] ``` 其中,filename表示生成的堆转储文件名。生成文件后,可以使用以下命令启动jhat进行分析: ``` jhat [filename] ``` 启动后,在浏览器中输入localhost:7000可以查看分析结果。 4. MAT工具 MAT(Memory Analyzer Tool)是一款开源的Java内存分析工具,可以使用以下命令进行安装: ``` sudo apt-get install eclipse-mat ``` 安装完成后,可以通过打开heap dump文件进行内存分析。 总之,对于JVM内存溢出问题,我们需要结合以上命令和工具进行全面的排查和分析,找到并解决具体的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值