简体   繁体   English

JFreeChart - Java 堆空间问题

[英]JFreeChart - Java Heap Space issue

I am using JFreeChart for the first time and I am using a TimeSeriesCollection() to create a TimeSeriesChart.我第一次使用 JFreeChart,我正在使用 TimeSeriesCollection() 创建一个 TimeSeriesChart。

My reslutset from the DB query is app.我来自数据库查询的 reslutset 是 app。 aroung 1000 records.约 1000 条记录。 I am using org.jfree.date.time.Minute.Minute(int min.....) object to add it to a TimeSeries object.我正在使用org.jfree.date.time.Minute.Minute(int min.....)对象将其添加到 TimeSeries 对象。

I have a JFrame on which I add the ChartPanel directly.我有一个 JFrame,我直接在上面添加了 ChartPanel。 The user will provide new input parameters and reload the chart data with new dataset.用户将提供新的输入参数并使用新数据集重新加载图表数据。 So I clean up before every reload by calling the following in a method所以我在每次重新加载之前通过在方法中调用以下内容进行清理

            dataset.removeAllSeries();
            chart.removeLegend();
            chart.getRenderingHints().clear();
            cp.getChartRenderingInfo().setEntityCollection(null);
            cp.removeAll();
            cp.revalidate();

The output is perfect.输出是完美的。 But I noticed that after running the program 'several times in Eclipse' I see the below error message about Java heap space.但我注意到,在“在 Eclipse 中多次”运行程序后,我看到了以下有关 Java 堆空间的错误消息。 Sometimes I also see in the Task Manager that the program hogs on the PC memory even though the dataset is very small (100 records).有时我也在任务管理器中看到,即使数据集非常小(100 条记录),程序也会占用 PC 内存。

Exception occurred during event dispatching:
java.lang.OutOfMemoryError: Java heap space
at sun.util.calendar.Gregorian.newCalendarDate(Gregorian.java:67)
at java.util.GregorianCalendar.<init>(GregorianCalendar.java:575)
at java.util.Calendar.createCalendar(Calendar.java:1012)
at java.util.Calendar.getInstance(Calendar.java:964)
at org.jfree.chart.axis.DateTickUnit.addToDate(DateTickUnit.java:238)
at org.jfree.chart.axis.DateAxis.refreshTicksHorizontal(DateAxis.java:1685)
at org.jfree.chart.axis.DateAxis.refreshTicks(DateAxis.java:1556)
at org.jfree.chart.axis.ValueAxis.reserveSpace(ValueAxis.java:809)
at org.jfree.chart.plot.XYPlot.calculateDomainAxisSpace(XYPlot.java:3119)
at org.jfree.chart.plot.XYPlot.calculateAxisSpace(XYPlot.java:3077)
at org.jfree.chart.plot.XYPlot.draw(XYPlot.java:3220)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1237)
at org.jfree.chart.ChartPanel.paintComponent(ChartPanel.java:1677)
at javax.swing.JComponent.paint(JComponent.java:1029)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5124)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1491)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1422)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:294)
at javax.swing.RepaintManager.paint(RepaintManager.java:1225)
at javax.swing.JComponent._paintImmediately(JComponent.java:5072)
at javax.swing.JComponent.paintImmediately(JComponent.java:4882)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:786)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:714)
at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:694)
at javax.swing.RepaintManager.access$700(RepaintManager.java:41)
at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1636)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:646)
at java.awt.EventQueue.access$000(EventQueue.java:84)
at java.awt.EventQueue$1.run(EventQueue.java:607)
at java.awt.EventQueue$1.run(EventQueue.java:605)
at java.security.AccessController.doPrivileged(Native Method)

My application is as follows:我的申请如下:

I have a JFrame on which I directly add the ChartPanel after passing a Chart to it.我有一个 JFrame,在将 Chart 传递给它后,我直接在其上添加了 ChartPanel。

chart = ChartFactory.createTimeSeriesChart("Peak monitor", , "Time: Zoom in", "# of Requests Logged", createDataset(from,to), true, false, false);

            chartpanel = new ChartPanel(chart);

            FramePanel.this.add(cp);


            validate();

Here createDataset(from, to) is a method这里createDataset(from, to)是一个方法

 private TimeSeriesCollection createDataset(Date from, Date to) {
    dataset.addSeries(controller.getStuff(from, to));
    return dataset;
}

getStuff is called within a SwingWorker thread (DIBkgd method)在 SwingWorker 线程中调用getStuff (DIBkgd 方法)

 public TimeSeries getStuff(Date from, Date to) {
    s1 = new TimeSeries("Log Requests");

    final Date from1 = from;
    final Date to1 = to;

    progressDialog.setVisible(true);

    sw = new SwingWorker<Void, Integer>() {

        @Override
        protected Void doInBackground() throws Exception {

            if (db.getCon() == null) {
                db.connect();
            }
            Arrlst2.clear();
            Arrlst2= db.getDataDB(from1, to1);

            for (Qryobjects x : Arrlst2) {                  
              s1.add(new Minute(x.getMinute(), x.getHour(), x.getDay(), x.getMonth(), x.getYear()), x.getCount());
            }

            System.out.println("finished fetching data");
            return null;
        }

        @Override
        protected void done() {
            progressDialog.setVisible(false);
        }
    };
    sw.execute();
    return s1;

}

Within my Database class the getDataDB is executed:在我的数据库类中执行getDataDB

 public List<Qryobjects> getDataDB(Date from, Date to) {

    PreparedStatement select;
    ResultSet rs;

    String selectSql = "Select Sum(Cnt) Cid, Hr, Min, Dat from (Select count(H.Request_Id) Cnt , To_Char(H.Timestamp,'HH24') HR, To_Char(H.Timestamp,'mm') MIN, To_Char(H.Timestamp,'MM-dd-yyyy') DAT From Status_History H Where H.Timestamp Between ? And ? Group By  H.Request_Id,  H.Timestamp Order By H.Timestamp Asc) Group By Hr, Min, Dat order by Dat asc";

    try {
        select = con.prepareStatement(selectSql);

        select.setDate(1, from);
        select.setDate(2, to);

        rs = select.executeQuery();

        System.setProperty("true", "true");

        while (rs.next()) {

            int cnt = rs.getInt("cid");

            int hour = Integer.parseInt(rs.getString("Hr"));
            int min = Integer.parseInt(rs.getString("Min"));
            int month = Integer.parseInt(rs.getString("dat").substring(0, 2));
             int day = Integer.parseInt(rs.getString("dat").substring(3, 5));
            int year = Integer.parseInt(rs.getString("dat").substring(6, 10));

             Arrlst1.add(new Qryobjects(cnt, hour, min, day, month,year));

        }
        rs.close();

    } catch (SQLException e) {
        e.printStackTrace();
    }

    return Arrlst1;
}

For reference, I profiled two long running time series DTSCTest and MemoryUsageDemo .作为参考,我分析了两个长时间运行的时间序列DTSCTestMemoryUsageDemo To exaggerate the scale, I used an artificially small heap, as shown below.为了夸大规模,我使用了一个人为的小堆,如下所示。 In each case, I saw the typical saw-tooth pattern of periodic garbage collection return to baseline, as shown here .在每一种情况下,我看到了定期的垃圾收集回到基线典型的锯齿模式,如这里 In contrast, this pathological example shows a secular rise in consumed memory from unrecoverable resources.相比之下,这个病态的例子表明,不可恢复的资源消耗的内存会长期上升。

$ java -Xms32m -Xmx80m -cp build/classes:dist/lib/* chart.DTSCTest
$ java -Xms32m -Xmx80m -jar jfreechart-1.0.14-demo.jar

I resolved my problem.我解决了我的问题。

I took the clue from @TrashGod to use dispose().我从@TrashGod 那里得到了使用 dispose() 的线索。 But it does not work directly for me.但它并不直接为我工作。

I was adding the chart panel directly to my main JFrame container.我将图表面板直接添加到我的主 JFrame 容器中。 And in my case, I wanted to keep creating the charts in the same JFrame container over and over.就我而言,我想在同一个 JFrame 容器中一遍又一遍地创建图表。

I first tried clearing the dataset and called removeall() on the chart panel, but it did not help.我首先尝试清除数据集并在图表面板上调用 removeall(),但没有帮助。

Then the solution I found was to create another JFrame and add the chart panel to it.然后我找到的解决方案是创建另一个 JFrame 并向其添加图表面板。 And when I closed this JFrame, I again clear the dataset and called removeall() on the chart panel and also called dispose().当我关闭这个 JFrame 时,我再次清除数据集并在图表面板上调用 removeall() 并调用 dispose()。 So everytime, I create a new chart, this JFrame and its children componenets are created and are completely disposed when I exit this JFrame.所以每次我创建一个新图表时,当我退出这个 JFrame 时,这个 JFrame 和它的子组件都会被创建和完全处理。

So, when a chart is created a new JFrame is created and then disposed.因此,当创建图表时,会创建一个新的 JFrame,然后对其进行处理。

I should also add that after making this change I started to see the Saw Tooth pattern in the Java VisualVM profiler.我还应该补充一点,在进行此更改后,我开始在 Java VisualVM 分析器中看到 Saw Tooth 模式。 I also used Jprofiler and I was shocked to see more than 100,000 objects were created while I was running my program.我还使用了 Jprofiler,当我运行我的程序时,我震惊地看到创建了超过 100,000 个对象。 Now, I see 9000 objects created and it remains constant for the JFree package and based on my resultset retrieved the number of objects in my database package increases or decreases.现在,我看到创建了 9000 个对象,它对于 JFree 包保持不变,并且根据我检索到的结果集,我的数据库包中的对象数量增加或减少。

One more thing I did was to make my SQL do the parsing and convert it to a number.我做的另一件事是让我的 SQL 进行解析并将其转换为数字。 I wanted to reduce the number of objects created and also reduce the processing done by my program for each retrieved record.我想减少创建的对象数量,并减少我的程序对每个检索到的记录所做的处理。

Your solution is great!你的解决方案很棒! :)) Thanks to you, I have fixed my heap overflow problem. :)) 谢谢你,我已经解决了我的堆溢出问题。 But, your solution can be even better.但是,您的解决方案可以更好。 :)) Before you draw your graph onto panel, just call method panel.RemoveAll(); :)) 在将图形绘制到面板上之前,只需调用方法panel.RemoveAll(); and everything that was before on your panel will be disposed.之前在您面板上的所有内容都将被处理掉。 No other JFrame instances are necessary... In my case, solution was:不需要其他JFrame实例......就我而言,解决方案是:

for(...)
{

    panel.RemoveAll();

    drawData(listOfData);

}

Have a nice day!祝你今天过得愉快! :) :)

In the method org.jfree.chart.axis.DateAxis.refreshTicksHorizontal, I added following extra lines to successfully avoided the OutOfmemoryError.在 org.jfree.chart.axis.DateAxis.refreshTicksHorizo​​ntal 方法中,我添加了以下额外的行以成功避免 OutOfmemoryError。 the reason is for some circumstance, the variable tickDate is not increased, so the loop of "while (tickDate.before(upperDate))" becomes an infinite loop.原因是在某些情况下,变量tickDate没有增加,所以“while (tickDate.before(upperDate))”的循环变成了无限循环。

protected List refreshTicksHorizontal(Graphics2D g2,
            Rectangle2D dataArea, RectangleEdge edge) {

    List result = new java.util.ArrayList();

    Font tickLabelFont = getTickLabelFont();
    g2.setFont(tickLabelFont);

    if (isAutoTickUnitSelection()) {
        selectAutoTickUnit(g2, dataArea, edge);
    }

    DateTickUnit unit = getTickUnit();
    Date tickDate = calculateLowestVisibleTickValue(unit);
    Date upperDate = getMaximumDate();

    boolean hasRolled = false;
    Date previousTickDate=null;            //added 
    while (tickDate.before(upperDate)) {
        if(previousTickDate!=null && tickDate.getTime()<=previousTickDate.getTime()){  //added 
            tickDate=new Date(tickDate.getTime()+100L); //added 
        }  //added 
        previousTickDate=tickDate; //added 
        //System.out.println("tickDate="+tickDate+" upperDate="+upperDate);**  //add to see infinite loop

Please try by removing the tooltips and the legend from the chart (making them 'false' in the constructor).请尝试从图表中删除工具提示和图例(在构造函数中将它们设为“假”)。 It should reduce the memory footprint它应该减少内存占用

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM