博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java: web应用中不经意的内存泄露
阅读量:6500 次
发布时间:2019-06-24

本文共 3634 字,大约阅读时间需要 12 分钟。

前面有一篇讲解,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下:

1、定义一个类App

package com.cnblogs.yjmyzz.web.controller;import java.util.Date;public class App {    boolean isRun = false;    public App() {        isRun = true;    }    public void start() {        while (isRun) {            System.out.println("=======> I AM ALIVE =>" + new Date());            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public void stop() {        isRun = false;    }}

代码里面的内容不是重点,只是示意一下,我打算在spring mvc 应用一启动时,就让这个类实例化,执行其中的start方法,即:每隔一秒输出一句话。

 

2、定义一个Listener

import com.cnblogs.yjmyzz.web.controller.App;import org.springframework.context.ApplicationListener;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.stereotype.Component;@Componentpublic class StartupListener implements ApplicationListener
{ App app; @Override public void onApplicationEvent(ContextRefreshedEvent evt) { if (evt.getApplicationContext().getParent() == null) { new Thread(new Runnable() { @Override public void run() { app = new App(); app.start(); } }).start(); } }}

代码也很简单,应用一启动,就开一个线程,实例化App,然后调用app.start()方法,运行一下,也跟预期的一样,每隔一秒输出类似下面的内容:

 =======> I AM ALIVE =>Wed Sep 16 21:55:42 CST 2015

正式部署到jboss上以后,问题来了,在jboss管理控制台上,把这个应用给disable甚至remove后,日志里仍然不断有上面的类似输出,即app的实例仍然活着,其start方法也始终在运行,换句话说,app并没有被销毁。

简单分析一下:jboss的每个server启动后,会伴随启动一个jvm实例,而部署在该server上的web应用,里面创建的各种资源也在这个jvm实例中,就算把应用给停掉甚至删除,由于代码中没有任何清除app或停止start方法的处理,所以这个实例一直存在,不会被销毁,除非server重启。

 

另一个问题:如果把上面这段代码中,创建线程的部分去掉,改成直接 app = new App(); app.start(); 部署时会发现另一个现象,日志里仍然不断有输出,即代码在执行,但是该应用在jboss中的状态始终是isdeploying,部署一直无法结束,始终处于『部署中』的状态。

原因:start方法中的Thread.sleep()方法会阻塞线程,导致部署无法执行完毕。

 

解决办法:

import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import javax.annotation.PreDestroy;import java.util.Date;@Componentpublic class App {    boolean isRun = false;    @PostConstruct    public void init() {        System.out.println("init ==> " + new Date());        isRun = true;    }    public void start() {        while (isRun) {            System.out.println("=======> I AM ALIVE =>" + new Date());            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public void stop() {        isRun = false;    }    @PreDestroy    public void destroy() {        System.out.println("destroy ==> " + new Date());        stop();    }}

这里做了几处改进:

a) 加上@Component后,App的实例将由Spring容器自动创建,即由容器来管理

b) 加上了@PreDestroy,Bean的生命周期由Spring容器来管理后,凡是Bean里加上该注解的方法,会在Bean销毁前被执行,通常该方法用于清理资源

c) 将初始化的工作,移到了init方法中,并通过@PostConstruct注解告诉Spring,在调用完Bean的默认构造方法后,自动来调用该方法(当然这一步是可选的,并非必须)

@Componentpublic class StartupListener implements ApplicationListener
{ @Autowired App app; @Override public void onApplicationEvent(ContextRefreshedEvent evt) { if (evt.getApplicationContext().getParent() == null) { new Thread(new Runnable() { @Override public void run() { app.start(); } }).start(); } }}

Listener中就简单多了,直接@Autowired注入app实例就行了。

 

个人建议:

a) 如果要在web 应用一启动时,就执行某些操作,特别是对资源类的长连接实例创建(比如:加载数据到缓存中预热、连接到Zookeeper监控节点变化、连接到Ftp准备取数据),最好交给Spring容器来自动创建,且务必记得在Destroy前,清理资源(即:断开连接)

b) 在启动的执行逻辑中,不要使用阻塞线程的操作(比如:Thread.sleep之类的方法),否则部署时,实际上代码已经在后台执行了,jboss管理控制台上,一直处于部署中的状态,也没有任何输出,让人一头雾水,折腾半天才能定位错误,很浪费时间,如果是线上生产环境,是要粗事情的。

转载地址:http://gmvyo.baihongyu.com/

你可能感兴趣的文章
Visual Studio Remote Debugger(for 2005/2008) .net远程调试<转>
查看>>
怎么获得combobox的valueField值
查看>>
浅谈C/C++中的static和extern关键字
查看>>
Console-算法[if,while]-一输入两个正整数m和n,求其最大公约数和最小公倍数
查看>>
浅谈网络协议(四) IP的由来--DHCP与PXE
查看>>
jre与jdk的区别
查看>>
全景图的种类
查看>>
git 维护
查看>>
jfinal框架下使用c3P0连接池连接sql server 2008
查看>>
Jfinal Generator 不需要生成带某个前缀的表名数组的方法
查看>>
struts2中使用标签操作静态方法等
查看>>
熬夜写了一个小游戏,向SpaceX聊表敬意
查看>>
身份证工具类
查看>>
JPA增删改查,
查看>>
apache 开启 gzip 压缩服务
查看>>
python mysql
查看>>
开源 免费 java CMS - FreeCMS1.5-建站向导
查看>>
Selenium的延迟等待
查看>>
jquery 1.6以上版本 全选
查看>>
AppCan 学习
查看>>