FYI,

feel free to jump in if you are interested.

Romain Manni-Bucau
@rmannibucau |  Blog | Github | LinkedIn | Tomitriber



---------- Forwarded message ----------
From: Romain Manni-Bucau <[email protected]>
Date: 2016-04-03 16:01 GMT+02:00
Subject: Re: JsonParserFactoryImpl holds 20mb heap memory when used as
jaxrs client in tomee
To: "[email protected]" <[email protected]>, ravi sankar
<[email protected]>


Hi Ravi,

2016-04-03 14:44 GMT+02:00 ravi sankar <[email protected]>:
> Hi Romain,
> Scenario: Retrieve the json file at 
> https://raw.githubusercontent.com/Ravisankar-Challa/tomee_embedded/master/src/main/resources/miscellaneousinfo.js
> using JaxRs client.
>
> git checkout master
>
> mvn -f tomee-embedded-shade.xml clean package -Dmaven.test.skip
>
> java -Xmx768M -jar target/tomeedemo.jar -c
>
> Using Apache Jmeter I tried firing 50 concurrent requests. 
> (Url:http://localhost:8080/api/client/test)
> Result: SEVERE: java.lang.OutOfMemoryError: Java heap space
>     javax.ws.rs.ProcessingException: java.lang.OutOfMemoryError: Java heap 
> space
>     at 
> org.apache.cxf.jaxrs.client.WebClient.handleResponse(WebClient.java:1187)
>

reproduced with the same JVM settings and:

import com.example.rest.JsonContentTypeResponseFilter;
import com.example.rest.model.misc.MiscellaneousInfo;
import org.apache.johnzon.jaxrs.JohnzonProvider;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class Run {
    public static void main(final String[] args) throws InterruptedException {
        final ExecutorService es = Executors.newFixedThreadPool(64);
        final ClientBuilder clientBuilder = ClientBuilder.newBuilder();
        clientBuilder.property("http.connection.timeout",
String.valueOf(30000));
        clientBuilder.property("http.receive.timeout", String.valueOf(30000));
        final Client client = clientBuilder.build().register(new
JohnzonProvider<>());
        final AtomicInteger errors = new AtomicInteger();
        for (int i = 0; i < 100; i++) {
            es.submit(() -> {
                try {
                    if
(client.target("https://raw.githubusercontent.com/Ravisankar-Challa/tomee_embedded/master/src/main/resources/miscellaneousinfo.js";)
                            .register(JsonContentTypeResponseFilter.class)
                            .request()
                            .get(MiscellaneousInfo.class).getTitles() == null) {
                        throw new IllegalArgumentException();
                    }
                } catch (final Exception e) {
                    e.printStackTrace();
                    errors.incrementAndGet();
                }
            });
        }
        es.shutdown();
        es.awaitTermination(1, TimeUnit.HOURS);
        System.out.println("Errors: " + errors.get());
    }
}



That said also setting -Dorg.apache.johnzon.max-string-length=4096
solved it - can be done outside the application, typically in
conf/system.properties.

> I have analyzed the heap dump using Ecilpse mat.
> One instance of "org.apache.johnzon.core.JsonReaderFactoryImpl" loaded by 
> "sun.misc.Launcher$AppClassLoader @ 0xc019df10" occupies 717,491,152 (91.98%) 
> bytes.
> The memory is accumulated in one instance of 
> "java.util.concurrent.ConcurrentLinkedQueue$Node" loaded by "<system class 
> loader>".
>
> valueBufferProvider of JsonParserFactoryImpl.java is holding all this memory.
>
> Auto adjust buffer Fix:
> JsonParserFactoryImpl.java line number 64this.valueBufferProvider = 
> getBufferProvider().newCharProvider(1);  //Set initial size to 1
>
> JsonStreamParserImpl.java line number 70
> private char[] fallBackCopyBuffer;
>
> JsonStreamParserImpl.java line number 562
> else if((endOfValueInBuffer - startOfValueInBuffer) > 
> fallBackCopyBuffer.length) {
>     System.out.println("Adjusting Buffer from "+fallBackCopyBuffer.length+" 
> to "+(endOfValueInBuffer-startOfValueInBuffer));
>     fallBackCopyBuffer = new char[endOfValueInBuffer - startOfValueInBuffer]; 
>  //Auto adjust to required size
> }
>

This doesn't solve the whole issue. of course then you have a small
buffer but now let's enrich your .js to have a string of ~10M (can
happen when you don't control what you call). Then you:

1. still have your issue
2. have no way to control the memory you can allocate (today you can
computing threads using the mapper x memory)


I'm happy to resize the default (just start a discussion on
dev@johnzon list) to something more reasonable (we have to check
twitter, github, jira at least) but I think this auto adjusting
solution is a seducing solution which can lead us to crossing fingers
in production which is probably the worse I can see when deploying an
application.

Wdyt?

> git checkout johnzon_buffer_fix
> mvn -f tomee-embedded-shade.xml clean package -Dmaven.test.skip
> java -Xmx768M -jar target/tomeedemo.jar -c
>
> Using Apache Jmeter I tried firing 50 concurrent requests. 
> (Url:http://localhost:8080/api/client/test)
> Using Ecilipse mat I analyzed the Heap Dump and didn't found any issues.
>
> Thanks,Ravisankar Challa
>
>     On Friday, 1 April 2016 1:23 AM, Romain Manni-Bucau 
> <[email protected]> wrote:
>
>
>  2016-03-31 15:58 GMT+02:00 ravi sankar <[email protected]>:
>> right now valueBufferProvider  is eating up 20mb by default, even for small 
>> json strings/numbers.
>> org.apache.johnzon.max-string-length is there to control the buffer size but 
>> how many people will be aware of this option. Every one expects the 
>> framework to do best with default settings :-)
>>
>
> I get it but this setting is important - this thread is not about that
> so let's not detail there - and if we use a lower value then at
> runtime, after 1 week of run you start to just fail and need to
> redeploy if you didnt identify it: this is way worse. If we implement
> the automatic adjustment: same punishment: it works, it works, OOME,
> restart, it works, OOME, restart, ...
>
> Side note: a switch of the impl from char[] to StringBuilder or
> equivalent is not an option cause it is insanely slow compared to
> current version, only possible auto adjustment would really be "oops
> it fails, can I increase?". If you want to give it a try for a patch
> (even without adding a min option but using min(8k, max) as min) I
> would be more than happy to review it but please ensure this behavior
> is logged explicitly at startup and at runtime in case of adjustment
> in INFO level - stating the buffer was not adapted and should be
> configured - each time the buffer is adjusted and probably use an
> ArrayList like algorithm (adjustment is not +1 char but size*2 in the
> limit of the max value). If your patch doesn't imply any slow down or
> is done thanks to a BufferProvider (= configurable) then I'm happy to
> push it upstream.
>
>
>
>>
>>    On Friday, 1 April 2016 12:32 AM, Romain Manni-Bucau 
>> <[email protected]> wrote:
>>
>>
>>  2016-03-31 15:26 GMT+02:00 ravi sankar <[email protected]>:
>>> Hi Romain,
>>>>setting a min-max means you'll use max only pretty quickly with 
>>>>auto-adjustment.Lets assume JsonParser has the following default buffer 
>>>>sizes of min value 1 and max 10 * 1024 * 1024.and needs to process a 
>>>>message like this {"hello":"hai"}
>>> Parser adjusts the buffer size to 5 to process the json field "hello". It 
>>> never gonna hit maximum value.
>>
>> What I mean if you will parse different json and will keep the bigger
>> size you visited. So you will likely be at max so min is useless. You
>> can adjust your max but any adjustment doesn't bring much in practise
>> excepted some memory instability.
>>
>>> Thanks,Ravi
>>>
>>>    On Thursday, 31 March 2016 11:45 PM, Romain Manni-Bucau 
>>> <[email protected]> wrote:
>>>
>>>
>>>  2016-03-31 14:34 GMT+02:00 ravi sankar <[email protected]>:
>>>> Hi Romain,
>>>
>>> Hello Ravi,
>>>
>>>>
>>>> I noticed the below issue when using JaxRsClient code.
>>>>
>>>> Looking in to the code of JsonParserFactoryImpl.java
>>>>
>>>>  public static final String MAX_STRING_LENGTH = 
>>>> "org.apache.johnzon.max-string-length";
>>>>  public static final int DEFAULT_MAX_STRING_LENGTH = 
>>>> Integer.getInteger(MAX_STRING_LENGTH, 10 * 1024 * 1024); //10m
>>>>
>>>> variable valueBufferProvider  is holding the 20 megabytes of data in heap.
>>>>
>>>> If my understanding is correct org.apache.johnzon.max-string-length 
>>>> determines the max length of the json field or json value when parsing.
>>>>
>>>> For general use cases json field names and values are very small strings 
>>>> unless we are trying to exchange files as binary data using json.
>>>>
>>>> Allocating 20 mega bytes of heap memory for processing field names or 
>>>> values is too big.
>>>>
>>>
>>> The original value was way smaller and it is actually not that big.
>>> You hit it very easily using public API like github one.
>>>
>>>> Is there any thing we can do to optimize it. like starting with small size 
>>>> and dynamically increase the valueBufferProvider size when ever required 
>>>> until it reaches a maximum value.
>>>>
>>>
>>> You can customize it for your own reader factory since that's a
>>> configuration the factory accepts. If you know you have small strings
>>> just set it to less.
>>>
>>>> I tried the option -Dorg.apache.johnzon.max-string-length=5 for the 
>>>> parsing the below json string
>>>> {
>>>>  "hello_world":"test"
>>>> }
>>>>
>>>> and got org.apache.johnzon.mapper.MapperException: Too many characters. 
>>>> Maximum string/number length of 5 exceeded
>>>>
>>>> Is it possible to have a min value and max value and exception should be 
>>>> thrown once the max string/number length is reached?
>>>
>>> That's current behavior excepted we don't auto-adapt. As a side note
>>> you can already wrap the jsonp API to auto adapt yourself if you get
>>> this exception if you care about buffer allocation. From experience
>>> setting a min-max means you'll use max only pretty quickly with
>>> auto-adjustment.
>>>
>>>>
>>>> Thanks,
>>>> Ravi
>>>>
>>>
>>>
>>>
>>
>>
>>
>
>
>

Reply via email to