alecharp.fr

java.lang.String limits

Facepalm with String limits S’il y a bien une chose que l’on peut très vite oublier avec Java, c’est la gestion de la mémoire. Et quand on fait des traitements lourds et longs, ça nous revient très rapidement en plein visage !

La limite que je pensais bien ne jamais rencontrer était bien celle-ci : la taille maximale d’une String

Reproduire le problème

Comment on fait pour l’atteindre ? Dans mon cas, on met dans une String l’intégralité des informations de chaque artefact d’un repository Maven. Plus simple, on agit de la sorte :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            try {
                sb.append("a");
            } catch (OutOfMemoryError e) {
                System.err.println(i);
                throw e;
            }
        }
        System.out.println(sb.toString());
    }
}

Je m’explique : dans la classe String, ce qui contient la chaîne de caractères c’est en fait un tableau de char. L’index du tableau, sous- entendu la taille de la String, est un int. Un int est stocké sous 32 bits, donc la valeur maximale que peut prendre un int est 2^31 -1 (cf la documentation en ligne : javadoc oracle)

Et donc si on arrive à mettre 2^31 caractères dans une String (via StringBuilder dans mon cas) on se retrouve avec une belle OutOfMemoryError.

Là, on fait :

Moralité de l’histoire

Ne jamais se croire à l’abris d’une OutOfMemoryError car, même avec du TDD, DDD ou autre, je ne pense qu’il existe (sinon il doit se sentir bien seul..) de développeur qui aurait fait un test unitaire à base de plusieurs millions de caratères..

Je dois avouer que je ne suis trouvé un peu con sur ce coup-là.

Nota bene

Un collègue (Olivier Croisier) m’a donné la bonne idée de passer par un Writer ainsi, je ne stocke pas de String et donc pas de OutOfMemoryError.

Autre fait important : l’output se fait en retour de HTTP GET. Avec un traitement potentiellement long (celui qui me met +2^31 caractères dans un String), on peut avoir des Timeout car la réponse n’est faite qu’en un coup. Avec le Writer, la réponse est donnée au fur et à mesure. En combinant ça et HTTP 1.1 (mode connecté), plus de Timeout. La vie est belle.