Jetty - za duża odpowiedź

0

Mam aplikacje restową i zwracam przez niego obrazki skonwertowane do byte array a pozniej do stringa zeby to wrzucic do json'a na wyjscie.
Jetty zwraca mi błąd jeśli obrazek jest większy niż około 20kb, dla mniejszych około 5kb działa bez problemu:

maj 02, 2018 3:29:02 PM org.glassfish.jersey.server.ServerRuntime$Responder writeResponse
SEVERE: An I/O error has occurred while writing a response message entity to the container output stream.
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
	at java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:57)
	at java.nio.ByteBuffer.allocate(ByteBuffer.java:335)
	at org.eclipse.jetty.util.BufferUtil.allocate(BufferUtil.java:108)
	at org.eclipse.jetty.io.ArrayByteBufferPool.acquire(ArrayByteBufferPool.java:69)
	at org.eclipse.jetty.server.HttpOutput.write(HttpOutput.java:291)
	at java.io.ByteArrayOutputStream.writeTo(ByteArrayOutputStream.java:167)
	at org.glassfish.jersey.message.internal.CommittingOutputStream.flushBuffer(CommittingOutputStream.java:307)
	at org.glassfish.jersey.message.internal.CommittingOutputStream.write(CommittingOutputStream.java:232)
	at org.codehaus.jackson.impl.Utf8Generator._flushBuffer(Utf8Generator.java:1754)
	at org.codehaus.jackson.impl.Utf8Generator._writeStringSegments(Utf8Generator.java:1213)
	at org.codehaus.jackson.impl.Utf8Generator._writeLongString(Utf8Generator.java:581)
	at org.codehaus.jackson.impl.Utf8Generator.writeString(Utf8Generator.java:550)
	at org.codehaus.jackson.map.ser.std.StringSerializer.serialize(StringSerializer.java:28)
	at org.codehaus.jackson.map.ser.std.StringSerializer.serialize(StringSerializer.java:18)
	at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446)
	at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150)
	at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
	at org.codehaus.jackson.map.ser.std.StdContainerSerializers$IndexedListSerializer.serializeContents(StdContainerSerializers.java:122)
	at org.codehaus.jackson.map.ser.std.StdContainerSerializers$IndexedListSerializer.serializeContents(StdContainerSerializers.java:71)
	at org.codehaus.jackson.map.ser.std.AsArraySerializerBase.serialize(AsArraySerializerBase.java:86)
	at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446)
	at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150)
	at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
	at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610)
	at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256)
	at org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:1613)
	at org.codehaus.jackson.jaxrs.JacksonJsonProvider.writeTo(JacksonJsonProvider.java:559)
	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:263)
	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:250)
	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
	at org.glassfish.jersey.filter.LoggingFilter.aroundWriteTo(LoggingFilter.java:293)
	at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)

0

Czytać nie umiesz? Wszystko jest napisane. "java.lang.OutOfMemoryError: Requested array size exceeds VM limit"

0
Trzeźwy Kaczor napisał(a):

Czytać nie umiesz? Wszystko jest napisane. "java.lang.OutOfMemoryError: Requested array size exceeds VM limit"

Ale bystry jesteś... ;) Chodzi mi o to jak tego uniknąć i móc wysłać klientowi coś większego.

1

Ogólnie to nie jest dobry pomysł wysyłać obrazki w JSONIe.
Zasadnicze pytanie to jakiej wielkości jest ten array przed wrzucaniem do Stringa/JSONa.
20/40kb to nie tak dużo, żeby Javę wywalić. Bo twój bład nawet nie oznacza braku pamięci, tylko że tablica jest za duża dla JVM - raczej przy gigabajtach allokowanych się powinno pojawić. (na 64 bitowej maszynie).

Sprawdź ten rozmiar jaki jest faktycznie i podaj wersję JVM.

0
jarekr000000 napisał(a):

Ogólnie to nie jest dobry pomysł wysyłać obrazki w JSONIe.
Zasadnicze pytanie to jakiej wielkości jest ten array przed wrzucaniem do Stringa/JSONa.
20/40kb to nie tak dużo, żeby Javę wywalić. Bo twój bład nawet nie oznacza braku pamięci, tylko że tablica jest za duża dla JVM - raczej przy gigabajtach allokowanych się powinno pojawić. (na 64 bitowej maszynie).

Sprawdź ten rozmiar jaki jest faktycznie i podaj wersję JVM.

Rozmiar tablicy bajtowej dla obrazka to 88593.

Wersja JVM:

java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
0

a nie możesz tego strimować ?

1

Weź dojedź na debugu do tej pechowej linijki w HeapByteBuffer i zobacz jaki jest wyliczony rozmiar tablicy. Coś tu mocno śmierdzi.

0

Kurde coś dziwnego bo zapinam w tym miejscu debuga i ciągle paramatry metody są jako undefined ;/

0
Świetny Jeleń napisał(a):

Kurde coś dziwnego bo zapinam w tym miejscu debuga i ciągle paramatry metody są jako undefined ;/

Jedynie co udało mi sie namierzyć to w ByteBuffer linia 335:

    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }

parametr capacity ma wartosc 0 xD

0

A możesz wrzucić reprodukowalny mały runner na githuba ? takie że robie klik i wybucha .

0

Niestety ciężko bo cały serwer śmiga z szyfrowaniem, baza itp a wycinanie działajacego samego resta troche by mi zajęło.
Może wycinki kodu coś rozjaśnią.

Klasa restowa:

import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
***

@Path("/pictures")
public class PicturesRest {

	@POST
	@Path("getPictures")
	@Secured
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response getPictures(PicturesQuery picturesQuery) {
		ResultWrapper<Picture> result = PicturesProvider.getPictures(picturesQuery);
		Pictures picturesOut = new Pictures();
		picturesOut.setId(picturesQuery.getObjectId());
		List<Picture> pictures = new ArrayList<>();
		
		for(ObjectPicture picture : result.getResults()) {
			Picture picture = new Picture();
			picture.setImage(picture.getImage().array()); // getImage zwraca ByteBuffer
			pictures.add(picture);
		}		
		
		picturesOut.setPictures(pictures);

		return Response.ok(picturesOut).build(); **// TUTAJ LECI WYJĄTEK**
	}
}

Metoda picture.setImage(...):

public void setImage(byte[] image) {
		if(image != null) {
			this.image = Base64.encodeBase64String(image);
		}
	}
1
Świetny Jeleń napisał(a):
public void setImage(byte[] image) {
		if(image != null) {
			this.image = Base64.encodeBase64String(image);
		}
	}

Głupie... ale jesteś pewny, że nie spotkała Cię kara za voida i ta metoda dostaje nulla, w efekcie czego this.image pozostaje nieustawione?

0
jarekr000000 napisał(a):
Świetny Jeleń napisał(a):
public void setImage(byte[] image) {
		if(image != null) {
			this.image = Base64.encodeBase64String(image);
		}
	}

Głupie... ale jesteś pewny, że nie spotkała Cię kara za voida i ta metoda dostaje nulla, w efekcie czego this.image pozostaje nieustawione?

Sprawdziłem nie trafia null.

0

Javę można skonfigurować tak, żeby sama raportowała tzw. "heap dump".
Ale zważywszy na poprzednie posty to może być za skomplikowane.
Proponuję dodać logowanie dostępnej pamięci na starcie aplikacji i tuż przed feralną linijką.
Np. https://stackoverflow.com/questions/12807797/java-get-available-memory
https://cruftex.net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your-Java-Benchmarks.html
i od razu w wypadku trafienia:
https://alvinalexander.com/blog/post/java/java-xmx-xms-memory-heap-size-control

0

Ile wynosi picturesOut.pictures.size()? Bo na moje oko to tam więcej niż jeden tych obrazków leci.

W Javie limitem długości tablicy jest Integer.MAX-1 (nie wiem czy na wszystkich platformach)
Jeżeli obrazków masz ponad jakieś 24000 to może być to. 24k * 88k bytów

INT_MAX-1 : https://ideone.com/rH90oO
INT_MAX-2: https://ideone.com/7fSHZY
EDIT: OK Ideone to nie najlepsze miejsce do sprawdzania takich rzeczy ale widać, że gdy się zmniejszy MAX_INT o np. 2 to leci Heap size limit zamiast array size.

0
nie100sowny napisał(a):

Ile wynosi picturesOut.pictures.size()? Bo na moje oko to tam więcej niż jeden tych obrazków leci.

W Javie limitem długości tablicy jest Integer.MAX-1 (nie wiem czy na wszystkich platformach)
Jeżeli obrazków masz ponad jakieś 24000 to może być to. 24k * 88k bytów

INT_MAX-1 : https://ideone.com/rH90oO
INT_MAX-2: https://ideone.com/7fSHZY
EDIT: OK Ideone to nie najlepsze miejsce do sprawdzania takich rzeczy ale widać, że gdy się zmniejszy MAX_INT o np. 2 to leci Heap size limit zamiast array size.

Na chwilę obecną lecą 2 obrazki jeden który ma 5kb i drugi który ma około 52kb.

0

po zrzuceniu tablic byte z obrazkow do stringów to pierwszy string ma dlugosc: 4252 a drugi string: 118124.

0

Możesz pokazać klasę Pictures?

0
import org.apache.commons.codec.binary.Base64;

public class Picture {
	private String image;

	public String getImage() {
		return image;
	}

	public void setImage(String image) {
		this.image = image;
	}
	
	public void setImage(byte[] image) {
		if(image != null) {
			this.image = Base64.encodeBase64String(image);
		}
	}

}
0
import java.util.List;


public class Pictures {
	private List<Picture> pictures;
	private String objectId;

	public List<Picture> getPictures() {
		return pictures;
	}

	public void setPictures(List<Picture> pictures) {
		this.pictures = pictures;
	}

	public String getObjectId() {
		return objectId;
	}

	public void setObjectId(String objectId) {
		this.objectId= objectId;
	}
}
2

Krótko.
Nieważne ile masz lub nie masz pamięci - ten problem oznacza, że próbujesz allokować bufor o wielkości Integer.MAX_MAXVALUE, coś musiało pójśc nie tak.
Podpowiedź od Heinza Kabutza jest taka - może masz jakiś race condition (nie widać tego w przesłanym kodzie, ale może cos uprościłes ? ).

0

Hmm nie mam pojęcia ale może podeśle jeszcze inicjalizacje serwera:

	int serverPort = ServerConfiguration.configuration.getServerPort();
		SslContextFactory sslContextFactory = new SslContextFactory();
		sslContextFactory.setKeyStorePath("src/main/resources/NOWY.jks");
		sslContextFactory.setKeyStorePassword("*************");
		SslConnectionFactory sslConne = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.toString());
		
		HttpConfiguration http_config = new HttpConfiguration(); 
		http_config.setSecureScheme("https");
		http_config.setSecurePort(serverPort);
		http_config.setOutputBufferSize(Integer.MAX_VALUE);
		http_config.setRequestHeaderSize(8192);
		http_config.setResponseHeaderSize(8192);
		
		HttpConfiguration https_config = new HttpConfiguration(http_config);
		https_config.addCustomizer(new SecureRequestCustomizer());
		
		HttpConnectionFactory cf = new HttpConnectionFactory(https_config);
				
		ServletHolder jerseyServlet2 = new ServletHolder(new ServletContainer(new RestApplication()));
        //rest
        ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
        servletContext.setContextPath("/");
        servletContext.addServlet(jerseyServlet2, "/*");

        ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
        contextHandlers.setHandlers(new Handler[]
        {servletContext});
		
		Server server = new Server();
		ServerConnector connector = new ServerConnector(server, sslConne, cf);
		connector.setPort(serverPort);
		server.addConnector(connector);
		server.setHandler(contextHandlers);
              // dwie linie nizej dodąłem na probe ale nic to nie dało
		server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize", "100000000"); 
		server.setAttribute("org.eclipse.jetty.server.Response.maxFormContentSize", "100000000");

		try {
			server.start();
			server.dump(System.err);
			server.join();
		} catch (Exception e) {
			System.out.println(e);
		}
0

JAk cos to post wyżej to mój tylko zmieniło mi nick :)

0

próbowałem wystreamować obraz ale dalej wyskakuje ten sam komunikat

return Response.ok(new ByteArrayInputStream(queryResult.getResults().get(1).getImage().array())).build();

getImage zwaraca ByteBuffer

0

może lepiej by było zrobić webview z wyświetlaniem tych obrazków? albo coś jak to ogarnąć bo przez stoi mój projekt a nie mam już pomysłów... :|

0

Próbowałem ogarnąc to jakiś czs temu , ale nie widze co się może dziać.
Podejrzenia:

  • błąd tkwi gdzieś w kodzie, którego nie pokazałeś,
  • masz zrypaną maszynę,

Jakbyś dał rade projekt wystawić na githubie. Taki, żeby się w nim problem reprodukował, to pewnie by się dało pomóc.

0

Próbowałbym przed tą linijką return Response.ok zserializować sobie ręcznie zwracany obiekt i wypisać go do logu. Stack trace sugeruje, że to się uda. Potem można próbować serializować pojedyńczo same obrazki, w zależności od rezultatu eksperymentu. Albo podrzucić jetty zserializowany przez nas ciąg bajtów/znaków zamiast obiektu. No i obserwować reakcję.

0

W sobotę spróbuje to wydzielić i wrzucić na githuba.
Ale wcześniej pisaliście że lepiej jakbym to streamował. Klientem resta jest aplikacja android to czy nie lepiej by było zrobić że pod danym adresem był by zwracany obrazek i wyświetlony on w aplikacji w webview? :

public class ImageServlet extends HttpServlet {
	
	 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	    { 
		 	String uuid = request.getParameter("imageID");
		 	byte[] image =  ImageProvider.getImage(UUID.fromString(uuid));
		 	response.setContentType("image/jpeg");
		 	response.setContentLength(image.length);
		 	
		 	OutputStream out = response.getOutputStream();
		 	out.write(image);
		 	out.close();
	    }
}

Czy lepiej to słać restem jak wcześniej próbowałem?

1 użytkowników online, w tym zalogowanych: 0, gości: 1