问题出现
最近一个项目中,有个需求功能是从外部传入的XML读取数据,然后写入到数据库中。
写完之后,有在本地电脑上的Tomcat跑了一下,正常读取到XML中的数据,并整理好后,插入了数据库保存了。但是线上运行的时候,经常会出现因为这个功能而导致Tomcat直接闪退的问题。
一开始以为是线上环境,外部传入的XML文件数量比较多,在读取XML文件列表的时候有问题,因为用的是递归方法在多层目录中查找所有XML文件,后面发现,目录层次不深,递归不应该出问题。
之后在本地电脑上搞了几百个XML文件(每个7MB左右)做测试,还是能正常读取完并且写入数据库。有一轮测试的时候,不小心看了一下本地电脑的内存使用情况,发现运行过程中,内存使用从7.8G一路涨到了9.4G,之后一直平稳保持到运行结束,又降回7.8G。
这时候,问题就很明显了,读取XML文件的过程消耗内存太严重,线上Tomcat的总堆内存才1GB,肯定是撑不住的。
这引起了我的好奇心,这里一个XML也才7MB,读取的时候到底能消耗多少内存?
于是打开了JV(jdk/bin目录下jvisualvm.exe)。给程序读取XML文件的地方打上断点,监测结果如下:
发现了Eden Space(新生代空间)最大空间340M(为了和线上环境一样,本地电脑的Tomcat总堆内存也被我设置为1GB了,按Tomcat默认规则Eden空间就是1/3大小),读取一个XML文件直接就用了150M,占用Eden内存达到了190M,再来一个直接到满了,所以只要GC释放稍慢一点,直接就跑不动了(我本地电脑GC快,所以还是能正常跑完,就是GC次数比较多)
之后,查了下资料,发现我用的dom读取法,是比较占用内存的,但是这个方法能修改XML内容(不过我这个需求功能不需要),另外的SAX比较不耗内存。
于是就换了下XML的读取方式,终于是让线上的Tomcat(堆内存1GB)也能正常读取完大量XML文件了。下面是2种方法,在本地读取1000个XML的监测结果,GC次数差了一倍:
下面来说下,改后的读取方式使用,用的是JAVA自带的JAXBContext(javax.xml.bind.JAXBContext)
1、首先需要一个和XML结构相同的对象,如果是多层节点的,就需要对象引用对象了。
@XmlRootElement(name = "ROOT") //注解法指定根元素/对象与元素绑定,大小写需要一致;也可以换设置法
public static class Root{
BookVo bookVo; //java对象中的属性名可以和节点名不一致
public BookVo getBookEle() { //对象必做有get/set,且需要和XML节点名对应,<ROOT><bookEle>...</bookEle></ROOT>
return viosurveilVo;
}
public void setBookEle(BookVo bookVo) {
this.viosurveilVo = viosurveilVo;
}
}
public static BookVo{...}
//解析指定xml文件
private Root analysisXML(String xmlFilePath) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StreamSource source = new StreamSource(new File(xmlFilePath));
JAXBElement<Root> jaxbElement = unmarshaller.unmarshal(source, Root.class); //指定法,指定根元素/对象与元素绑定
Root root = jaxbElement.getValue(); //直接得到可操作JAVA对象
//Root root = (Root) unmarshaller.unmarshal(new File(xmlFilePath)); //使用@注解法,只需要这句,不需要上面3句
//ViosurveilVo viosurveilVo = root.getViosurveil();
return root;
}catch (Exception e) {
return null;
}
}