异常原因:
01-Feb-2018 19:41:28.020 WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [xxx] appears to have started a thread named [FileWatchdog] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.lang.Thread.sleep(Native Method)
org.apache.log4j.helpers.FileWatchdog.run(FileWatchdog.java:104)
问题起因:
1.在web.xml里面配置了
<context-param>
<param-name>contextLog4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
这样
org.springframework.web.util.Log4jConfigListener会在初始化log的时候进行初始化 org.apache.log4j.helpers.FileWatchdog的子类( org.apache.log4j.PropertyWatchdog[log4j配置源为.properties时]或者org.apache.log4j.xml.XMLWatchdog【log4j 配置源为.xml时】 优先为.xml)
/** Parameter specifying the refresh interval for checking the log4j config file */
public static final String REFRESH_INTERVAL_PARAM = "log4jRefreshInterval";
省略中......
// Check whether refresh interval was specified.
String intervalString = servletContext.getInitParameter(REFRESH_INTERVAL_PARAM);
if (StringUtils.hasText(intervalString)) {
// Initialize with refresh interval, i.e. with log4j's watchdog thread,
// checking the file in the background.
try {
long refreshInterval = Long.parseLong(intervalString);
Log4jConfigurer.initLogging(location, refreshInterval);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid 'log4jRefreshInterval' parameter: " + ex.getMessage());
}
}
在Log4jConfigurer.initLogging(location, refreshInterval);这个方法里面初始化监控狗:
public static void initLogging(String location, long refreshInterval) throws FileNotFoundException {
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
File file = ResourceUtils.getFile(resolvedLocation);
if (!file.exists()) {
throw new FileNotFoundException("Log4j config file [" + resolvedLocation + "] not found");
}
if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
DOMConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
}
else {
PropertyConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
}
}
static
public
void configureAndWatch(String configFilename, long delay) {
XMLWatchdog xdog = new XMLWatchdog(configFilename);
xdog.setDelay(delay);
xdog.start();
}
或者:
static
public
void configureAndWatch(String configFilename, long delay) {
PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
pdog.setDelay(delay);
pdog.start();
}
造成这个问题的描述参考:http://www.bubuko.com/infodetail-1812902.html
说一下解决方案吧:1.自定义FileWatchdog的子类 编写一个stopFileWatchdog方法,在stop方法里面引用 钩子 interrupted 设为true
这样就可以结束FileWatchdog自定义子类的run方法了
子类代码:
private static ContextPropertyWatchdog contextPropertyWatchdog;
private volatile boolean interrupted = false;
private ContextPropertyWatchdog(String filename) {
super(filename);
}
public static synchronized ContextPropertyWatchdog getContextPropertyWatchdog(String filename){
if (null == contextPropertyWatchdog){
contextPropertyWatchdog = new ContextPropertyWatchdog(filename);
}
return contextPropertyWatchdog;
}
public void doOnChange() {
new PropertyConfigurator().doConfigure(filename,
LogManager.getLoggerRepository());
}
public void stopWatchdog(){
this.interrupted = true;
}
public void run() {
while(!this.interrupted) {
try {
Thread.sleep(delay);
} catch(InterruptedException e) {
// no interruption expected
}
checkAndConfigure();
}
}
}
调用:
监听器初始化方法中: contextInitialized
public void contextInitialized(ServletContextEvent servletContext) {
String location = servletContext.getServletContext().getInitParameter("log4jConfigLocation");
String intervalString = servletContext.getServletContext().getInitParameter("contextLog4jRefreshInterval");
if (StringUtils.hasText(location) && StringUtils.hasText(intervalString)) {
try {
long refreshInterval = Long.parseLong(intervalString);
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
File file = ResourceUtils.getFile(resolvedLocation);
if (!file.exists()) {
throw new FileNotFoundException("Log4j config file [" + resolvedLocation + "] not found");
} else {
stringName = file.getAbsolutePath();
ContextPropertyWatchdog pDog = ContextPropertyWatchdog.getContextPropertyWatchdog(stringName);
pDog.setDelay(refreshInterval);
pDog.start();
}
} catch (NumberFormatException var5) {
throw new IllegalArgumentException("Invalid 'log4jRefreshInterval' parameter: " + var5.getMessage());
} catch (FileNotFoundException var6) {
throw new IllegalArgumentException("Invalid 'log4jConfigLocation' parameter: " + var6.getMessage());
}
}
}
销毁方法中:contextDestroyed
if ( !"".equals(stringName) ){
ContextPropertyWatchdog contextPropertyWatchdog = ContextPropertyWatchdog.getContextPropertyWatchdog(stringName);
contextPropertyWatchdog.stopWatchdog();
contextPropertyWatchdog.join();
logger.info("=========================log4j 配置文件监控 退出====================");
}
其中stringName是自己定义的一个常量:
private String stringName = "";
这个就可以完成这个功能了,但是这样有个缺点就是在tomcat - shutdown的时候会先等待监控狗sleep醒来之后在停止tomcat,时间会稍微多一点。
对了web.xml文件也进行了修改为:
<context-param>
<param-name>contextLog4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>