今天接到任务,图片上传服务器的性能有问题,高峰期间YGC频率在2秒一次,维护应用的程序员诊断的原因是图片处理API有性能问题。
不管咋样,亲自看一把才是王道,jmap -dump出堆内存文件。
用Eclipse MAT打开一看,发现FileCleaningTracker这个对象占用了将近一半的堆内存。
查看了下代码,发现这个类是commons-io下用于追踪文件的一个方法引起的问题。方法如下:
/**
* Track the specified file, using the provided marker, deleting the file
* when the marker instance is garbage collected.
* The speified deletion strategy is used.
*
* @param path the full path to the file to be tracked, not null
* @param marker the marker object used to track the file, not null
* @param deleteStrategy the strategy to delete the file, null means normal
* @throws NullPointerException if the path is null
*/
public void track(String path, Object marker, FileDeleteStrategy deleteStrategy) {
if (path == null) {
throw new NullPointerException("The path must not be null");
}
addTracker(path, marker, deleteStrategy);
}
/**
* Adds a tracker to the list of trackers.
*
* @param path the full path to the file to be tracked, not null
* @param marker the marker object used to track the file, not null
* @param deleteStrategy the strategy to delete the file, null means normal
*/
private synchronized void addTracker(String path, Object marker, FileDeleteStrategy deleteStrategy) {
// synchronized block protects reaper
if (exitWhenFinished) {
throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
}
if (reaper == null) {
reaper = new Reaper();
reaper.start();
}
trackers.add(new Tracker(path, deleteStrategy, marker, q));
}
发现每当创建文件的时候该方法都会创建一个Reaper线程,代码如下:
/**
* Run the reaper thread that will delete files as their associated
* marker objects are reclaimed by the garbage collector.
*/
@Override
public void run() {
// thread exits when exitWhenFinished is true and there are no more tracked objects
while (exitWhenFinished == false || trackers.size() > 0) {
try {
// Wait for a tracker to remove.
Tracker tracker = (Tracker) q.remove(); // cannot return null
trackers.remove(tracker);
if (!tracker.delete()) {
deleteFailures.add(tracker.getPath());
}
tracker.clear();
} catch (InterruptedException e) {
continue;
}
}
}
}
只有当主线程手动调用exitWhenFinished方法的时候,这些Reaper线程才会进行清理动作。这样在图片上传高峰期将会在内存中积累很大的垃圾数据。
解决方案:
创建临时文件之后不调用该方法,写脚本事后进行删除工作。