简体   繁体   中英

Base64 value causing StringBuilder.append out of memory error in SAX parser

I am using SAX parser to parse the XML file which is the response from client's server. tag has inside the Base64 decoded value of the files (jpg, png, pdf etc.). Some of them I can easily write to file but with some of them (I am guessing the large one) I have a problem during StringBuilder.append on the overwritten method of DefaultHandler:

@Override
public void characters(char[] ch, int start, int length) throws SAXException {
    if (valueActive) { // set to true only when <value> tag is active
        stringBuilder.append(ch, start, length); // OUT OF MEMORY error
    }
}

On the next steps, I am using Base64Encoder to write files:

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {

    if (qName.equalsIgnoreCase("value")) {

        valueActive = false;

        try (OutputStream stream = new FileOutputStream(my file here)) {

        base64 = Base64.getDecoder().decode(stringBuilder.toString());

        stream.write(base64);
   } catch .... / omitted part of code
}

Can anyone suggest how to solve out of memory issue? Stack trace:

Caused by: java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:3332)
        at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
        at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:596)
        at java.lang.StringBuilder.append(StringBuilder.java:190)
        at com....AttachmentHandler.characters(AttachmentHandler.java:47)

EDIT:

Base64 is from XML:

<value> String encoded with base64 </value>

For SAX parser I am using RestTemplate:

@Override
@Retryable
public Application performMultimediaRequest(String url) {

    SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();

    try {
        SAXParser saxParser = saxParserFactory.newSAXParser();

        restTemplate.execute(url,
                HttpMethod.GET,
                null,
                responseExtractor -> {
                    try {
                        saxParser.parse(responseExtractor.getBody(), attachmentHandler);
                    } catch (SAXException e) {
                        log.error("Cannot parse API response with SAX parser");
                    }
                    return null;
                }
        );
    } catch (SAXException | ParserConfigurationException e) {
        log.error("Cannot parse response with SAX parser");
    }

    return attachmentHandler.getApplication();
}

Either increase the JVM heap memory to accommodate StringBuilder memory requirements or use Base64.getDecorder().wrap(InputStream) method:

Returns an input stream for decoding Base64 encoded byte stream.

The read methods of the returned InputStream will throw IOException when reading bytes that cannot be decoded.

Closing the returned input stream will close the underlying input stream.

The problem with your example code is that it's incomplete and we don't know from where you are getting the char value. If you are getting the char value from InputStream you have the work cut out for wrap() .

You can hope to decrease the overall memory requirements by switching from Base64.getDecorder().decode(String) to Base64.getDecorder().decode(byte[]) but if you want to save the heap memory you should rewrite your code to use InputStream and don't create temporary String or byte[] .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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