java.lang.OutOfMemoryError: Java heap space (JUnit test)

5.9k views Asked by At

I have a class,which i use input stream and then conver it to string. Also i have a Junit test,where i get the exception " java.lang.OutOfMemoryError: Java heap space",I know that the problem is that I have a memory leak in the test, but I don’t understand how to fix it.Maybe someone know how to do it?

My class:

public class IntegrationMonitoringHttpInterceptor implements ClientHttpRequestInterceptor
{

    private static final Logger LOG = LoggerFactory.getLogger(IntegrationMonitoringHttpInterceptor.class);

    @Resource
    private IntegrationMessageService integrationMessageService;

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] bytes, final ClientHttpRequestExecution clientHttpRequestExecution) throws IOException
    {
        final String requestMethod = request.getMethod().toString();
        final String requestURI = request.getURI().toString();
        final String requestHeaders = request.getHeaders().toString();
        final String requestBody = new String(bytes, StandardCharsets.UTF_8);
        final String requestMessageBody = requestMethod + "\n" + requestURI + "\n" + requestHeaders + "\n" + requestBody;

        final IntegrationMessageBatchModel batch = saveRequest(requestURI, requestMessageBody);

        try
        {
            final ClientHttpResponse response = clientHttpRequestExecution.execute(request, bytes);

            final String responseStatusCode = response.getStatusCode().toString();
            final String responseStatusText = response.getStatusText();
            final String responseHeaders = response.getHeaders().toString();
            final InputStream responseInputStream = response.getBody();
            final String responseBody = IOUtils.toString(responseInputStream, StandardCharsets.UTF_8.name()); //in the test, a memory leak occurs here
            IOUtils.closeQuietly(responseInputStream);
            responseInputStream.close();
            final String responseMessageBody = responseStatusCode + "\n" + responseStatusText + responseHeaders + "\n" + responseBody;


            saveResponse(responseMessageBody, batch, requestURI);

            return response;
        }
        catch (Exception exception)
        {
            integrationMessageService.createMessageInBatch(exception.getMessage(), batch);
            LOG.warn("Cannot execute request due to unexpected error", exception);
            throw exception;
        }

    }

    private IntegrationMessageBatchModel saveRequest(final String requestURI, final String requestMessageBody)
    {
        try
        {
            IntegrationMessageBatchModel integrationMessageBatchModel = integrationMessageService.createBatch(IntegrationExchangeType.HTTP, requestURI);
            integrationMessageService.createMessageInBatch(requestMessageBody, integrationMessageBatchModel);

            return integrationMessageBatchModel;
        }
        catch (Exception exception)
        {
            LOG.warn("Cannot save message due to unexpected error", exception);
            return null;
        }
    }

    private void saveResponse(final String response, final IntegrationMessageBatchModel batchModel, final String requestURI)
    {
        try
        {
            if (batchModel != null)
            {
                integrationMessageService.createMessageInBatch(response, batchModel);
            }
            else
            {
                IntegrationMessageBatchModel integrationMessageBatchModel = integrationMessageService.createBatch(IntegrationExchangeType.HTTP, requestURI);
                integrationMessageService.createMessageInBatch(response, integrationMessageBatchModel);
            }
        }
        catch (Exception exception)
        {
            LOG.warn("Cannot save message due to unexpected error", exception);
        }
    }
}

My JUnit test:

@RunWith(MockitoJUnitRunner.class)
@UnitTest
public class IntegrationMonitoringHttpInterceptorTest
{
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Mock
    private IntegrationMessageService integrationMessageService;

    @Mock
    private HttpRequest httpRequest;

    @Mock
    private ClientHttpRequestExecution clientHttpRequestExecution;

    @Mock
    private ClientHttpResponse clientHttpResponse;


    @InjectMocks
    private IntegrationMonitoringHttpInterceptor integrationMonitoringHttpInterceptor = new IntegrationMonitoringHttpInterceptor();

    @Test
    public void intercept() throws Exception
    {

        final String body = "http message";
        final URI uri = new URI("http://headers.jsontest.com/");
        final InputStream inputStream = new InputStream()
        {
            @Override
            public int read()
            {
                return 0;
            }
        };
        when(httpRequest.getURI()).thenReturn(uri);
        when(httpRequest.getMethod()).thenReturn(HttpMethod.POST);
        when(httpRequest.getHeaders()).thenReturn(new HttpHeaders());
        when(clientHttpRequestExecution.execute(httpRequest, body.getBytes(StandardCharsets.UTF_8))).thenReturn(clientHttpResponse);
        when(clientHttpResponse.getStatusCode()).thenReturn(HttpStatus.OK);
        when(clientHttpResponse.getStatusText()).thenReturn("OK");
        when(clientHttpResponse.getHeaders()).thenReturn(new HttpHeaders());
        when(clientHttpResponse.getBody()).thenReturn(inputStream);
        when(integrationMessageService.createBatch(eq(IntegrationExchangeType.HTTP), eq(uri.toString())))
                .thenReturn(new IntegrationMessageBatchModel());
        when(integrationMessageService.createMessageInBatch(eq(body), any(IntegrationMessageBatchModel.class)))
                .thenReturn(new IntegrationMessageModel());
        when(integrationMessageService.createMessageInBatch(contains(body), any(IntegrationMessageBatchModel.class)))
                .thenReturn(new IntegrationMessageModel());

        integrationMonitoringHttpInterceptor.intercept(httpRequest, body.getBytes(StandardCharsets.UTF_8), clientHttpRequestExecution);

        verify(clientHttpRequestExecution, times(1)).execute(httpRequest, body.getBytes(StandardCharsets.UTF_8));
        verify(integrationMessageService, times(1)).createBatch(eq(IntegrationExchangeType.HTTP), eq(uri.toString()));
        verify(integrationMessageService, times(2)).createMessageInBatch(anyString(), any(IntegrationMessageBatchModel.class));
    }

Stacktrace:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2367)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:535)
    at java.lang.StringBuilder.append(StringBuilder.java:204)
    at org.apache.commons.io.output.StringBuilderWriter.write(StringBuilderWriter.java:138)
    at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2002)
    at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1980)
    at org.apache.commons.io.IOUtils.copy(IOUtils.java:1957)
    at org.apache.commons.io.IOUtils.copy(IOUtils.java:1907)
    at org.apache.commons.io.IOUtils.toString(IOUtils.java:778)
    at org.apache.commons.io.IOUtils.toString(IOUtils.java:803)
    at core.interceptors.IntegrationMonitoringHttpInterceptor.intercept(IntegrationMonitoringHttpInterceptor.java:48)
    at core.interceptors.IntegrationMonitoringHttpInterceptorTest.intercept(IntegrationMonitoringHttpInterceptorTest.java:85)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:110)
    at org.junit.rules.RunRules.evaluate(RunRules.java:18)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:69)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:48)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
1

There are 1 answers

0
Joni On BEST ANSWER

Your mock input is an infinitely long stream of NUL (zero) bytes. At some point there's an attempt to read all of it into memory, which of course makes you run out of memory because it's infinitely long:

    final InputStream inputStream = new InputStream()
    {
        @Override
        public int read()
        {
            return 0;
        }
    };

A quick fix is to limit it to a known size like 100 bytes

final InputStream inputStream = new ByteArrayInputStream(new byte[100]);