Какой самый эффективный способ памяти для записи из базы данных в (zip) файл в Java? - PullRequest
2 голосов
/ 14 сентября 2010

Моя программа достаточно быстрая, но я бы предпочел отказаться от этой скорости для оптимизации памяти, поскольку максимальное использование памяти одним пользователем достигает 300 МБ, что означает, что лишь немногие из них могут постоянно вызывать сбой приложения.Большинство ответов, которые я нашел, были связаны с оптимизацией скорости, а другие были просто общими («если вы пишете напрямую из базы данных в память, не должно быть много использования памяти»).Ну, кажется, что есть :) Я думал о том, чтобы не публиковать код, чтобы не «блокировать» чьи-то идеи, но, с другой стороны, я мог бы тратить ваше время, если вы не видите, что я уже сделалтак вот:

// First I get the data from the database in a way that I think can't be more 
// optimized since i've done some testing and it seems to me that the problem 
// isn't in the RS and setting FetchSize and/or direction does not help.

public static void generateAndWriteXML(String query, String oznaka, BufferedOutputStream bos, Connection conn)
        throws Exception
{
    ResultSet rs = null;
    Statement stmt = null;
    try
    {
        stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
        rs = stmt.executeQuery(query);
        writeToZip(rs, oznaka, bos);
    } finally
    {
        ConnectionManager.close(rs, stmt, conn);
    }
}

// then I open up my streams. In the next method I'll generate an XML from the
// ResultSet and I want that XML to be saved in an XML, but since its size takes up
// to 300MB, I want it to be saved in a ZIP. I'm thinking that maybe by writing 
// first to file, then to zip I could get a slower but more efficient program.

private static void writeToZip(ResultSet rs, String oznaka, BufferedOutputStream bos)
        throws SAXException, SQLException, IOException
{
    ZipEntry ze = new ZipEntry(oznaka + ".xml");
    ZipOutputStream zos = new ZipOutputStream(bos);
    zos.putNextEntry(ze);
    OutputStreamWriter writer = new OutputStreamWriter(zos, "UTF8");
    writeXMLToWriter(rs, writer);
    try
    {
        writer.close();
    } catch (IOException e)
    {
    }
    try
    {
        zos.closeEntry();
    } catch (IOException e)
    {
    }
    try
    {
        zos.flush();
    } catch (IOException e)
    {
    }
    try
    {
        bos.close();
    } catch (IOException e)
    {
    }
}

// And finally, the method that does the actual generating and writing. 
// This is the second point I think I could do the memory optimization since the
// DataWriter is custom and it extends a custom XMLWriter that extends the standard
// org.xml.sax.helpers.XMLFilterImpl I've tried with flushing at points in program,
// but the memory that is occupied remains the same, it only takes longer.

public static void writeXMLToWriter(ResultSet rs, Writer writer) throws SAXException, SQLException, IOException
{
    //Set up XML
    DataWriter w = new DataWriter(writer);
    w.startDocument();
    w.setIndentStep(2);
    w.startElement(startingXMLElement);
    // Get the metadata
    ResultSetMetaData meta = rs.getMetaData();
    int count = meta.getColumnCount();
    // Iterate over the set
    while (rs.next())
    {
        w.startElement(rowElement);
        for (int i = 0; i < count; i++)
        {
            Object ob = rs.getObject(i + 1);
            if (rs.wasNull())
            {
                ob = null;
            }
            // XML elements are repeated so they could benefit from caching
            String colName = meta.getColumnLabel(i + 1).intern();
            if (ob != null)
            {
                if (ob instanceof Timestamp)
                {
                    w.dataElement(colName, Util.formatDate((Timestamp) ob, dateFormat));
                }
                else if (ob instanceof BigDecimal)
                {
                    // Possible benefit from writing ints as strings and interning them
                    w.dataElement(colName, Util.transformToHTML(new Integer(((BigDecimal) ob).intValue())));
                }
                else
                {   // there's enough of data that's repeated to validate the use of interning
                    w.dataElement(colName, ob.toString().intern());
                }

            }
            else
            {
                w.emptyElement(colName);
            }
        }
        w.endElement(rowElement);
    }
    w.endElement(startingXMLElement);
    w.endDocument();
}

РЕДАКТИРОВАТЬ: Вот пример использования памяти (взятый с visualVM):

Memory usage screenshot

РЕДАКТИРОВАТЬ2: База данных Oracle10.2.0.4.и я установил ResultSet.TYPE_FORWARD_ONLY и получил максимум 50 МБ!Как я сказал в комментариях, я буду следить за этим, но это действительно многообещающе.

Memory usage after adding  ResultSet.TYPE_FORWARD_ONLY

РЕДАКТИРОВАТЬ3: Кажется, есть еще одна возможная оптимизация.Как я уже сказал, я генерирую XML, что означает, что много данных повторяется (если не что иное, как теги), то есть String.intern () может помочь мне в этом, я опубликую его, когда протестирую это.

Ответы [ 3 ]

3 голосов
/ 14 сентября 2010

Можно ли использовать ResultSet.TYPE_FORWARD_ONLY?

Вы использовали ResultSet.TYPE_SCROLL_INSENSITIVE.Я считаю, что для некоторых баз данных (вы не сказали, какую из них вы используете) это приводит к загрузке всего набора результатов в память.

0 голосов
/ 15 сентября 2010

Я выполнил еще несколько тестов и сделал следующие выводы:

  1. Наибольший выигрыш - в JVM (или у visualvm проблемы с контролем пространства Java 5 Heap :).Когда я впервые сообщил, что ResultSet.TYPE_FORWARD_ONLY принес мне значительный выигрыш, я ошибся.Наибольшее преимущество получило использование Java 5, при котором та же программа использовала до 50 МБ пространства памяти, в отличие от Java 6, в которой тот же код занимал до 150 МБ.
  2. Второе усиление в ResultSet.TYPE_FORWARD_ONLY, которое заставило программу занимать как можно меньше памяти.
  3. Третье усиление в Sting.intern (), которое заставило программу занимать немного меньше памятипоскольку он кэширует строки вместо создания новых.

Это использование с оптимизацией 2 и 3 (если бы не было String.intern (), график был бы таким же, вы должны толькодобавьте еще 5 МБ к каждой точке)

alt text

и это использование без них (меньшее использование в конце связано с тем, что программе не хватает памяти :)) alt text

Спасибо всем за помощь.

0 голосов
/ 14 сентября 2010

Так как это Java, память должна только всплывать временно, если вы не пропускаете ссылки, например, если вы помещаете вещи в список, являющийся членом единственного объекта, который имеет жизненный цикл всей программы, или, по моему опыту, более вероятен утечка ресурсов, которая происходит, когда (и это, я предполагаю, относится к Java, хотя я думаю о C #), объекты, которые используют неуправляемые ресурсы, такие как файловые дескрипторы, никогда не вызывают свой код очистки, условие, обычно вызываемое пустыми обработчиками исключений, которые не re-throw к родительскому стековому фрейму, который имеет чистый эффект обхода блока finally ...

...