Geheugengebruik van integers in Java

De Java-engine van een van onze applicaties bevat een gefacetteerde zoekmachine die zijn data in-memory houdt om snel resultaten te kunnen leveren. De indexen van die zoekmachine bestaat uit een aantal Maps en Sets gevuld met Integers. Dat blijkt echter niet zo’n heel efficiënte manier te zijn als je kijkt naar het geheugengebruik.

Om te testen hoe veel geheugen een Set met Integers gebruikt heb ik deze testcode die een Set met 100.000 integers samenstelt. Door de hoeveelheid gebruikt geheugen daarvoor en daarna te vergelijken valt te meten hoe veel geheugen die Set kost:

package memtest;

import java.util.HashSet;
import java.util.Set;

public class MemTest {

    public static long getUsedMem() {

        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();

    }

    public static void main(String[] args) {

        long initialMem = getUsedMem();

        Set set = new HashSet();

        for (int i = 1; i <= 100000; i++) {
            set.add(i);
        }

        long finalMem = getUsedMem();

        System.out.println("Extra mem: " + (finalMem - initialMem));

    }

}

Dit leverde bij mij na een aantal keer draaien de volgende output:

Extra mem: 6655960
Extra mem: 6655960
Extra mem: 6655944
Extra mem: 6727368
Extra mem: 6742968
Extra mem: 6658232

Een Set met 100.000 Integers kost in dit geval dus ongeveer 6MB oftewel gemiddeld 66 bytes per stuk. Hoe komt dat? Een integer is toch maar 4 bytes?

Het korte antwoord is dat als je een Set vult met integers, deze automatisch worden ‘geboxed’ naar Integer objecten en dat een object meer geheugen kost dan een ‘primitive’ integer. Bij een geringe hoeveelheid items in zo’n Set zul je dit niet snel merken maar in ons geval hebben we tientallen sets met tienduizenden integers. Dan loopt het geheugengebruik al snel op.

Een oplossing hiervoor is geen gebruik te maken van een Set (die kan namelijk alleen met objecten overweg) maar bijvoorbeeld gebruik te maken van Trove. Die biedt een keur aan Collections klassen die onder water gebruik maken van primitieve variabelen. Dat is niet alleen sneller maar kost ook nog eens minder geheugen.

Om dit te testen passen we bovenstaande code aan naar het volgende:

package memtest;

import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;

public class MemTestTrove {

    public static long getUsedMem() {

        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();

    }

    public static void main(String[] args) {

        long initialMem = getUsedMem();

        TIntSet set = new TIntHashSet();

        for (int i = 1; i <= 100000; i++) {
            set.add(i);
        }

        long finalMem = getUsedMem();

        System.out.println("Extra mem: " + (finalMem - initialMem));

    }

}

Na deze code een aantal keer te draaien blijkt er nog minder dan een derde van de hoeveelheid geheugen nodig is en ook verrassend constant is:

Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208

Dat scheelt! Code die sneller draait en minder geheugen kost. Maar ja; dat levert wel een extra dependency op. En een ‘rare’ interface want een TSet is niet hetzelfde als een Set uit de standaard API. In ons geval konden we de publieke API ‘schoon’ houden door daar gebruik te blijven maken van de standaard Set interface maar onder water worden de Trove classes gebruikt.

 

 

Rabo OmniKassa ondersteunt geen SNI bij automatic­Response­Url

Per 1 april 2015 stopt de Rabobank met de Rabo Internetkassa. Als vervanger daarvan is er de door de Rabobank zelf ontwikkelde OmniKassa. Werkt ongeveer hetzelfde; het initiëren van transacties gebeurt door een client naar de omgeving van de OmniKassa te sturen met een ‘signed’ request met behulp van een geheime sleutel. De OmniKassa controleert of de ondertekening klopt en biedt de client vervolgens de mogelijkheid om af te rekenen. Nadat er is betaald komt men terug op de website en kun je controleren of de betaling is geslaagd.

Nu wil het gebeuren dat de client na het betalen direct weg klikt en niet terug komt op de website. Om in die gevallen toch te zien dat er is betaald kun je bij het initiëren van de transactie een automaticResponseUrl meegeven die de OmniKassa zelf moet aanroepen om de transactiestatus door te geven.

Maar nu komt het; als je webshop draait op HTTPS werken de aanroepen naar de automaticResponseUrl alleen als die zonder SNI (Server Name Indication) werkt. De OmniKassa (althans het deel dat de automatische notificaties uitvoert) draait op Java 6. Dat kan ik in de logs zien bij de transacties waarbij de automatische notificaties wel werkten:

XXX.XXX.XXX.XXX – – [17/Feb/2015:09:32:08 +0100] “POST /?module=SHOP&action=ProcessPayment&orderId=XXX HTTP/1.1” 302 – “-” “Java/1.6.0_45”

Van Java 6 is bekend dat deze geen SNI ondersteunt. In mijn geval kon ik het probleem oplossen door de automaticResponseUrl te laten wijzen naar een ander domein dat wel werkt zonder SNI. Een structurele oplossing kan alleen van de kant van Rabobank komen; door gebruik te maken van een HTTP client die ook SNI ondersteunt voor HTTPS-requests. Het is immers 2015 en in de huidige stand van de techniek ondersteunt 99% van onze bezoekers SNI. De OmniKassa echter nog niet.

Ik begrijp uiteraard dat dat niet zomaar geregeld is maar ik zou in de tussentijd in ieder geval de documentatie van de OmniKassa voorzien van een melding dat voor requests naar de automaticResponseUrl geen SNI kan worden gebruikt.

– Edit: Rabobank heeft een update uitgevoerd waardoor SNI nu wel wordt ondersteund.

Now accepting HTTP/2 connections

This blog now runs on a server that accepts HTTP/2 connections. It is H2O 1.0.0 with a proxy to Apache/PHP.

Since H2O can only proxy to HTTP/1 using plain (no HTTPS) connections, WordPress needs to be told not to redirect to the canonical url. This can be done by adding the following line to wp-config.php: $_SERVER['HTTPS'] = 1;

Building H2O requires quite some knowlegde of Linux. And I wouldn’t recommend using it in production yet. But it surely is fun to experiment with.

Another day – another WordPress Blog

I’m a developer. I know I should be able to build my own blogging software. But over the years I have learnt to stand on shoulders of others. The WordPress community is a great one. And even though a WordPress site is a honeypot for malware and hackers, I prefer to just start blogging than spend more time coding than actually writing.

So here it is. On a fresh VPS. HTTPS and IPv6 in place. Enjoy.

My $0.02 on ipv6 day 2012

Ok, so today is ipv6 day. Again.

But as long as access providers do not offer ipv6 by default, I seriously wonder who I should make my website ipv6-compatible for. Sounds like a catch-22 to me.

However, I may have a useful tip for sysadmins out there. Whenever you rent a VPS you may already have an ipv6 address assigned to it. And if you’re like me, you limit SSH access to it, for instance using iptables. But since your box also has an ipv6 address that it is listening to, do not forget to make changes to ip6tables also.

The huge ipv6 address space makes it almost impossible to find your box using a port scanner, but neighbor VPS-es may be able to guess your address really quickly. So don’t think ipv6 gives you extra security, just more obscurity.

So go fix those ipv6 firewalls! Or disable ipv6 networking until you actually start using it.

Substrings in javascript – the IE compatible way

Here’s something that might cause some web developers bang their heads against walls. Internet Explorer behaves incorrectly when substr() is called with a negative start value. It simply returns the whole string instead of N characters from the end.

So instead of calling substr() for the N last characters of a string like this:

var lastPart = myString.substr(-10);

You might want to call it like this:

var lastPart = myString.substr(myString.length - 10);

This way it works the same in all browsers.