内存泄漏修改前代码
public static String prepareCTUEventPayloadForBatch(List<TradeDetailModel> modelList) {
List<TradeEvent> payloadList = new ArrayList<TradeEvent>();
for (TradeDetailModel model : modelList) {
// 循环构建交易事件
TradeEvent payload = constructTradeEvent(model);
if (payload != null) {
payloadList.add(payload);
}
}
if (payloadList == null || payloadList.size() == 0) {
return null;
}
XStream stream = new XStream();
return stream.toXML(payloadList);
}
每个请求都会new一个XStream对象,然后xstream内部又会new一个CompositeClassLoader,并且Class.forName调用该loader
minor gc不会回收这种class loader对象,那就会导致heap被占满并full gc了。
真正内存泄漏的本质原因是:由于大量的new出来很多xstream对象,xstream内部new出来CompositeClassLoader,
并且Class.forName调用该loader。minor gc不会回收这种class loader对象,存在可达且无用对象。这才是内存泄漏的本因。
合并支付系统中代码修改后:
由于静态类级别的xstream对象,xstream内部new出来一个CompositeClassLoader,
并且Class.forName调用该loader。虽然xstream内部new出来一个CompositeClassLoader存在堆区,但是静态finnal级别的xstream对象不在堆区,
因此不存在可达且无用对象,从而minor gc会回收这种class loader对象。
主业务路径系统,在xstream使用上不存在内存泄漏的,基本采用如下使用方式。
class A
private static final XStream stream = new XStream();
new一个XStream对象,然后xstream内部只会new一个CompositeClassLoader
二,JAVA内存相关名词
1、 堆栈(stack)。位于通用RAM中,但通过它的“堆栈指针”可以从处理器哪里获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些JAVA数据存储在堆栈中——特别是对象引用,但是JAVA对象不存储其中。
2、 堆(heap)。一种通用性的内存池(也存在于RAM中),用于存放所有的JAVA对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。但是用堆进行存储分配比用堆栈进行存储存储需要更多的时间。
3、 静态存储(static storage)。这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。
总结:1、堆:所谓的动态内存,其中的内存在不需要时可以回收,以分配给新的内存请求。内存中的数据是无序的,即先分配的和随后分配的内存并没有什么必然的位置关系,释放时也可以没有先后顺序。一般由使用者自由分配,malloc分配的就是堆,需要手动释放。
堆栈: 只有一个出入口的队列,即后进先出(First In Last Out,先分配的内存必定后释放。一般由系统自动分配,存放函数的参数值,局部变量等,自动清除。
2、堆:静态、全局和new得到的变量都放在堆中;堆栈:局部变量放在堆栈中,每个函数进入的时候分一小块,函数返回的时候就释放了,所以函数返回,局部变量就全没了。
4、内存泄漏:应用程序分配了某段内存后且程序已不再使用该段内存,由于设计或程序失误失去了对该段内存的控制能力。一般所说的内存泄漏是指的堆内存泄漏。(参考上面堆阐述。)
三、Java Designer take care of your memory
1、垃圾收集器GC
Java的一个重要优点就是通过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,只是它的表现与C++不同。
2、通过监控对象状态决定是否释放对象极其内存
Java中,程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由GC决定和执行的。在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。释放对象的根本原则就是该对象不再被引用。
3、GC机制-回收不可达对象,程序员不需考虑
为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
4、 GC机制-可达且无用对象,导致内存泄漏
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。