OutOfMemoryError 异常:Java 堆空间,如何调试...?

Posted

技术标签:

【中文标题】OutOfMemoryError 异常:Java 堆空间,如何调试...?【英文标题】:OutOfMemoryError exception: Java heap space, how to debug...? 【发布时间】:2011-03-24 17:00:34 【问题描述】:

我收到 java.lang.OutOfMemoryError 异常:Java 堆空间。

我正在解析一个 XML 文件,存储数据并在解析完成后输出一个 XML 文件。

收到这样的错误我有点惊讶,因为原始 XML 文件一点也不长。

代码:http://d.pr/RSzp 档案:http://d.pr/PjrE

【问题讨论】:

您的程序中可能存在错误,请尝试针对 OOM 错误执行堆转储以获得适度的最大内存大小(因此转储不会那么大) [我仍在等待提供实时内存分析/监控建议的答案。一个(简单的)工具可以走很长的路。有一些 SO 问题涵盖了只是这个。] 另见(搜索“java配置文件内存”):***.com/questions/5349062/…***.com/questions/756873/…***.com/questions/46642/… 【参考方案1】:

解释为什么你有一个 OutOfMemoryError 的简短答案,对于在文件中找到的每个质心,你循环遍历已经“注册”的质心以检查它是否已知(添加一个新的或更新已经注册的) .但是对于每一个失败的比较,你都会添加一个新质心的新副本。因此,对于每个新质心,它添加它的次数与列表中已有质心一样多,然后您遇到添加的第一个质心,更新它并离开循环......

这是一些重构的代码:

public class CentroidGenerator 

    final Map<String, Centroid> centroids = new HashMap<String, Centroid>();

    public Collection<Centroid> getCentroids() 
        return centroids.values();
    

    public void nextItem(FlickrDoc flickrDoc) 

        final String event = flickrDoc.getEvent();
        final Centroid existingCentroid = centroids.get(event);

        if (existingCentroid != null) 
            existingCentroid.update(flickrDoc);
         else 
            final Centroid newCentroid = new Centroid(flickrDoc);
            centroids.put(event, newCentroid);
        
    


    public static void main(String[] args) throws IOException, SAXException 

        // instantiate Digester and disable XML validation
        [...]


        // now that rules and actions are configured, start the parsing process
        CentroidGenerator abp = (CentroidGenerator) digester.parse(new File("PjrE.data.xml"));

        Writer writer = null;

        try 
            File fileOutput = new File("centroids.xml");
            writer = new BufferedWriter(new FileWriter(fileOutput));
            writeOuput(writer, abp.getCentroids());

         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
         finally 
            try 
                if (writer != null) 
                    writer.close();
                
             catch (IOException e) 
                e.printStackTrace();
            
        

    

    private static void writeOuput(Writer writer, Collection<Centroid> centroids) throws IOException 

        writer.append("<?xml version='1.0' encoding='utf-8'?>" + System.getProperty("line.separator"));
        writer.append("<collection>").append(System.getProperty("line.separator"));

        for (Centroid centroid : centroids) 
            writer.append("<doc>" + System.getProperty("line.separator"));

            writer.append("<title>" + System.getProperty("line.separator"));
            writer.append(centroid.getTitle());
            writer.append("</title>" + System.getProperty("line.separator"));

            writer.append("<description>" + System.getProperty("line.separator"));
            writer.append(centroid.getDescription());
            writer.append("</description>" + System.getProperty("line.separator"));

            writer.append("<time>" + System.getProperty("line.separator"));
            writer.append(centroid.getTime());
            writer.append("</time>" + System.getProperty("line.separator"));

            writer.append("<tags>" + System.getProperty("line.separator"));
            writer.append(centroid.getTags());
            writer.append("</tags>" + System.getProperty("line.separator"));

            writer.append("<geo>" + System.getProperty("line.separator"));

            writer.append("<lat>" + System.getProperty("line.separator"));
            writer.append(centroid.getLat());
            writer.append("</lat>" + System.getProperty("line.separator"));

            writer.append("<lng>" + System.getProperty("line.separator"));
            writer.append(centroid.getLng());
            writer.append("</lng>" + System.getProperty("line.separator"));

            writer.append("</geo>" + System.getProperty("line.separator"));

            writer.append("</doc>" + System.getProperty("line.separator"));

        
        writer.append("</collection>" + System.getProperty("line.separator") + System.getProperty("line.separator"));

    

    /**
     * JavaBean class that holds properties of each Document entry. It is important that this class be public and
     * static, in order for Digester to be able to instantiate it.
     */
    public static class FlickrDoc 
        private String id;
        private String title;
        private String description;
        private String time;
        private String tags;
        private String latitude;
        private String longitude;
        private String event;

        public void setId(String newId) 
            id = newId;
        

        public String getId() 
            return id;
        

        public void setTitle(String newTitle) 
            title = newTitle;
        

        public String getTitle() 
            return title;
        

        public void setDescription(String newDescription) 
            description = newDescription;
        

        public String getDescription() 
            return description;
        

        public void setTime(String newTime) 
            time = newTime;
        

        public String getTime() 
            return time;
        

        public void setTags(String newTags) 
            tags = newTags;
        

        public String getTags() 
            return tags;
        

        public void setLatitude(String newLatitude) 
            latitude = newLatitude;
        

        public String getLatitude() 
            return latitude;
        

        public void setLongitude(String newLongitude) 
            longitude = newLongitude;
        

        public String getLongitude() 
            return longitude;
        

        public void setEvent(String newEvent) 
            event = newEvent;
        

        public String getEvent() 
            return event;
        
    

    public static class Centroid 
        private final String event;
        private String title;
        private String description;

        private String tags;

        private Integer time;
        private int nbTimeValues = 0; // needed to calculate the average later

        private Float latitude;
        private int nbLatitudeValues = 0; // needed to calculate the average later
        private Float longitude;
        private int nbLongitudeValues = 0; // needed to calculate the average later

        public Centroid(FlickrDoc flickrDoc) 
            event = flickrDoc.event;
            title = flickrDoc.title;
            description = flickrDoc.description;
            tags = flickrDoc.tags;
            if (flickrDoc.time != null) 
                time = Integer.valueOf(flickrDoc.time.trim());
                nbTimeValues = 1; // time is the sum of one value
                        
            if (flickrDoc.latitude != null) 
                latitude = Float.valueOf(flickrDoc.latitude.trim());
                nbLatitudeValues = 1; // latitude is the sum of one value
            
            if (flickrDoc.longitude != null) 
                longitude = Float.valueOf(flickrDoc.longitude.trim());
                nbLongitudeValues = 1; // longitude is the sum of one value
            
        

        public void update(FlickrDoc newData) 
            title = title + " " + newData.title;
            description = description + " " + newData.description;
            tags = tags + " " + newData.tags;
            if (newData.time != null) 
                nbTimeValues++;
                if (time == null) 
                    time = 0;
                
                time += Integer.valueOf(newData.time.trim());
            
            if (newData.latitude != null) 
                nbLatitudeValues++;
                if (latitude == null) 
                    latitude = 0F;
                
                latitude += Float.valueOf(newData.latitude.trim());
            
            if (newData.longitude != null) 
                nbLongitudeValues++;
                if (longitude == null) 
                    longitude = 0F;
                
                longitude += Float.valueOf(newData.longitude.trim());
            
        

        public String getTitle() 
            return title;
        

        public String getDescription() 
            return description;
        

        public String getTime() 
            if (nbTimeValues == 0) 
                return null;
             else 
                return Integer.toString(time / nbTimeValues);
            
        

        public String getTags() 
            return tags;
        

        public String getLat() 
            if (nbLatitudeValues == 0) 
                return null;
             else 
                return Float.toString(latitude / nbLatitudeValues);
            
        

        public String getLng() 
            if (nbLongitudeValues == 0) 
                return null;
             else 
                return Float.toString(longitude / nbLongitudeValues);
            
        

        public String getEvent() 
            return event;
        
    

【讨论】:

顺便说一句,看看您的代码,您的平均方法也是错误的:如果您有 4、4、4、4、4、4、4、4、2,您将返回 3 作为平均值。 .【参考方案2】:

可以尝试在 eclipse.ini 文件中将(我假设您使用 Eclipse)-Xms 和 -Xmx 值设置得更高。

例如)

-vmargs

-Xms128m //(初始堆大小)

-Xmx256m //(最大堆大小)

【讨论】:

不,我来自终端:java -vmargs -Xms128m -Xmx256m -cp .:jars/* CentroidGenerator data/data.xml --- 无法识别的选项:-vmargs 无法创建 Java 虚拟机器。 顺便说一句,我已经尝试过 java -Xms128m -Xmx1024m -cp .:jars/* CentroidGenerator data/data.xml 并且我得到了同样的错误 在 eclipse.ini 中设置值只会影响 eclipse 本身,不会影响它启动的 Java 程序。你设置-Xmx的方向是正确的,这是在Eclipse内部程序的启动配置中完成的 @Yoni 我没有使用 Eclise,我只是从终端运行我的应用程序,我正在使用建议的参数,但它不起作用,我仍然出现内存不足错误【参考方案3】:

如果这是您只想完成的一次性事情,我会尝试 Jason 的建议,即增加 Java 的可用内存。

您正在构建一个非常大的对象列表,然后遍历该列表以输出一个字符串,然后将该字符串写入文件。列表和字符串可能是您高内存使用的原因。您可以以更面向流的方式重新组织您的代码。在开始时打开文件输出,然后在解析每个 Centroid 时为其编写 XML。这样你就不需要保留一个大列表,也不需要保留一个代表所有 XML 的大字符串。

【讨论】:

是的,我得到了你的答案,唯一的问题是当在源文件中发现新的相关项目时,质心实际上会逐渐更新。换句话说,第一个质心可能需要连续更新,因此我不能只打印和发布它。【参考方案4】:

转储堆并对其进行分析。您可以使用-XX:+HeapDumpOnOutOfMemoryError 系统属性配置内存错误时的自动堆转储。

http://www.oracle.com/technetwork/java/javase/index-137495.html

https://www.infoq.com/news/2015/12/OpenJDK-9-removal-of-HPROF-jhat

http://blogs.oracle.com/alanb/entry/heap_dumps_are_back_with

【讨论】:

好吧,那个博客已经消失了,我也看不到缓存版本的文章了。链接另一篇文章,该文章提供了当前 Java 的基本命令,并解释了 Java 9 中的变化【参考方案5】:

回答“如何调试”问题

首先要收集您的帖子中缺少的信息。可能有助于未来遇到同样问题的人的信息。

首先,完整的堆栈跟踪。从 XML 解析器中抛出的内存不足异常与从您的代码中抛出的异常非常不同。

第二,XML文件的大小,因为“一点也不长”是完全没用的。是 1K、1M 还是 1G?有多少元素。

三、你是怎么解析的? SAX、DOM、StAX,完全不同的东西?

第四,您如何使用数据。您是在处理一个文件还是多个文件?您是否在解析后不小心持有数据?代码示例在这里会有所帮助(并且指向某些 3rd 方站点的链接对于未来的 SO 用户并不是非常有用)。

【讨论】:

1.好的 2. 我已经发布了文件,它是 328KB 3-4。我已经发布了代码,Commons-digester 是解析器【参考方案6】:

好的,我承认我正在用一个可能的替代方案来避免你的直接问题。您可能需要考虑使用 XStream 进行解析,而不是让它用更少的代码处理大部分工作。下面的粗略示例使用 64MB 堆解析您的 XML。请注意,它还需要 Apache Commons IO 才能轻松读取输入,以允许黑客将 &lt;collection&gt; 转换为 &lt;list&gt;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.apache.commons.io.FileUtils;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;

public class CentroidGenerator 
    public static void main(String[] args) throws IOException 
        for (Centroid centroid : getCentroids(new File("PjrE.data.xml"))) 
            System.out.println(centroid.title + " - " + centroid.description);
        
    

    @SuppressWarnings("unchecked")
    public static List<Centroid> getCentroids(File file) throws IOException 
        String input = FileUtils.readFileToString(file, "UTF-8");
        input = input.replaceAll("collection>", "list>");

        XStream xstream = new XStream();
        xstream.processAnnotations(Centroid.class);

        Object output = xstream.fromXML(input);
        return (List<Centroid>) output;
    

    @XStreamAlias("doc")
    @SuppressWarnings("unused")
    public static class Centroid 
        private String id;
        private String title;
        private String description;
        private String time;
        private String tags;
        private String latitude;
        private String longitude;
        private String event;
        private String geo;
    

【讨论】:

mhm 好的,但是 commons-digester 有什么问题?不只是像其他解析器一样的解析器吗?谢谢 @Patrick:这是一个很好的问题,但我没有答案。它要么效率低下,要么你的代码正在做一些需要大量内存的事情。浏览代码我没有看到任何明显的东西,它需要超过 1GB 似乎真的很奇怪。您可能想尝试使用像YourKit 这样的分析器来查看是什么消耗了这么多内存。或者只是使用jmap -histo 获取内存转储。【参考方案7】:

我下载了你的代码,我几乎从不这样做。我可以 99% 肯定地说,这个 bug 就在你的代码中:循环中的“if”不正确。它与 Digester 或 XML 没有任何关系。要么你犯了一个逻辑错误,要么你没有充分考虑要创建多少个对象。

但你猜怎么着:我不会告诉你你的错误是什么。

如果您无法从我上面给出的几个提示中弄清楚,那就太糟糕了。这与您通过没有提供足够的信息(在原始帖子中)而让所有其他受访者经历的情况相同,以实际开始调试。

也许您应该阅读 -- 实际阅读 -- 我以前的帖子,并使用它要求的信息更新您的问题。或者,如果您不愿意这样做,请接受您的 F。

【讨论】:

以上是关于OutOfMemoryError 异常:Java 堆空间,如何调试...?的主要内容,如果未能解决你的问题,请参考以下文章

2 线程“主”java.lang.OutOfMemoryError 中的异常:Java 堆空间

异常 java.lang.OutOfMemoryError:Java 堆空间

线程“Thread-8”java.lang.OutOfMemoryError 中的异常:Java 堆空间

Android:致命异常:java.lang.OutOfMemoryError

OutOfMemoryError异常 和 StackOverflowError异常

了解OutOfMemoryError异常 - 深入Java虚拟机读后总结