Java Finalization is Bullshit

The purpose of this article is to speak about some poorly understood mechanisms of the Java Virtual Machine. If you already know all these mechanisms, you should contact our RH to work at FastConnect ;-)

So please, consider the following program. If we run it with a java heap limited to 8Mb (thanks to the option -Xmx8m), it will fail with an ‘OutOfMemoryException’. But it will happen some time after 1000 loops ; other times after only 100 loops. It seems random.

class Padding
{
	private byte[] padding;
	Padding() {
		this.padding = new byte[1024*1024]; //1Mbyte
	}
	protected void finalize() throws Throwable {
		this.padding[0] = this.padding[1];
	}
}

public class Main
{
	public static void main(String[] args) throws Exception {
		int cpt = 0;
		try {
			for(;; ++cpt) {
				new Padding();
			}
		} catch (OutOfMemoryError oom) {
			System.err.println("oom after "+cpt+" loops.");
			throw oom;
		}
	}
}

Here a little quiz. Can you explain this behavior? Is there a leak in the program? Why the number of iterations before the OOM is random?

Before reading the following, I suggest you try to run this little program by yourself. Maybe you will have to increase the size of the padding array to reproduce the described behaviour.

The first question is not very difficult. The random behavior is due to the garbage collector. To provide good performance, recent Java VMs manage the memory at the same time as your code is executed. That’s why if the Java heap is not big enough it is possible to have an OOM even if there is no leak, even if the marked objects are always smaller than the java heap…

That’s why you must never be stingy with memory. A java program needs to have a lot of free memory to maximise safe and efficient memory management.

The next questions are more tricky. If we remove the ‘finalize method’, the program works fine, even with a very small heap. Can you explain why?

class Padding
{
	private byte[] padding;
	Padding() {
		this.padding = new byte[1024*1024]; //1Mbyte
	}
}

public class Main
{
	public static void main(String[] args) throws Exception {
		for(;;) {
			new Padding();
		}
	}
}

Again, I suggest you try to run this program. If you open ‘jconsole’ or ‘jvisualvm’, you should see a very abnormal memory activity: the curve is flat (but few very rare accidents). So, I have two questions:

  • Why the curve of this very simple program is so flat?
  • Why the existence of a ‘finalize method’ disrupts the memory management.

As a java expert, I expect you already read (several times) the documentation about the java garbage collector, its tuning and its algorithms.

The default GC uses a generational algorithm where the allocation in Eden generation is a simple stack allocation. With our simple program, Padding objects are never promoted from the young generation to the tenured generation during the collection. It means that objects are fastly allocated in stack, then the Copy GC (that copies the survivor eden objects into the survivor spaces) is triggered and it copies nothing. In this particular – but not rare situation – the java memory management is more efficient than the C++ malloc because allocations and deallocations are held in stack and batched.

I hope my explanations were clear. If not, you should read the following article: Java theory and practice: Urban performance legends, revisited.

But we have not answered the last and most tricky question: why the existence of a ‘finalize method’ disrupts the memory management. A hint is in the title of this article (this has nothing to do with the bull of course). Please consider the following program:

class Padding
{
	static java.util.Set<Padding> retention = new java.util.HashSet<Padding>();
	private byte[] padding;
	private int id;
	Padding(int id) {
		this.id = id;
		this.padding = new byte[1024*1024]; //1Mbyte
	}
	protected void finalize() throws Throwable {
		System.err.println("finalize is called for Padding[id="+id+"]");
		retention.add(this);
	}
}

public class Main
{
	public static void main(String[] args) throws Exception {
		int cpt = 0;
		try {
			for(;; ++cpt) {
				new Padding(cpt);
			}
		} catch (OutOfMemoryError oom) {
			System.err.println("oom after "+cpt+" loops.");
			throw oom;
		}
	}
}

Again I suggest you test it several times with different heap size (-Xmx64m for example). Its behaviour is very stable: it fails all of the time at the same moment.

On blogs and forums, I often read “finalize is called when the object is reclaimed”. This is wrong! When a finalizable object is no more referenced, the JVM adds it to the JVM’s finalization queue. At some point later, the JVM’s finalizer thread will dequeue it, call its ‘finalize method’, and record that its finalizer has been called. It is only when the garbage collector rediscovers that the object is unreachable that it reclaims it.

This behaviour is a big issue: it makes the code of the Garbage Collector very complex, it is error prone for the JVM providers and it consumes a lot of memory. It is the main reason why the Finalization is not in the Java specification for mobile phones and (credit) card, for example.

Elsewhere there is no good usage of the Java Finalization. For example, if you close sockets thanks to a ‘finalize method’, you will have a big problem if you have a lot of free memory but not enough file descriptors. Garbage Collectors are good for managing the managed memory and only that.

You can refer to this article which explains how finalization works and how not to use it.

Cyril Martin (mcoolive).

Bookmark and Share

Les fonctions récursives terminales

Il y a quelques temps déjà, je discutais avec un collègue dans ce lieu de socialisation qu’est la machine à café. Nous ne parlions pas de notre travail, mais nous parlions tout de même d’informatique. Nous eûmes l’échange suivant :

  • Les langages fonctionnels, c’est nul ; les fonctions récursives ça peut casser la pile d’exécution.
  • Bah non, si tu utilises une fonction récursive terminale…
  • Une quoi ?

Les mots ne sont pas exacts, mais je crois être fidèle à nos intentions. Depuis j’ai eu d’autres occasions de vérifier que cette notion de programmation était souvent très mal connue. D’où mon idée d’écrire un article sur le sujet.

Qu’est-ce qu’une fonction récursive terminale ?

Une fonction récursive terminale est une fonction qui lorsqu’elle fait appel à elle-même, le fait en tout dernier. Concrètement il n’y a pas de calcul entre l’appel récursif et l’instruction return. Le terme anglais est « tail-end recursion ».

Il s’agit donc d’une manière d’écrire votre fonction récursive.

À quoi ça sert ?

Cela sert à produire du code efficace, clair, et qui ne risque pas de faire déborder la pile d’exécution.

D’un point de vue algorithmique, une fonction récursive terminale est équivalente à une simple boucle. Écrire ainsi vos fonctions récursives permettra au compilateur d’optimiser efficacement le code produit.

Dans un langage impératif, le programmeur a souvent l’idée d’utiliser une boucle, donc la question est moins importante. En revanche, dans un langage fonctionnel comme OCaml ou parallelC#, qui vous invite par sa syntaxe et son esthétisme à user de fonctions, il est primordial d’écrire des fonctions terminales.

Un exemple commenté : la fonction ‘factorielle’ en langage C

Pour illustrer mon propos, j’ai choisi de commenter plusieurs implémentations de la fonction « factorielle », écrites en langage C.

La plupart des programmeurs (dé)formés par des années de pratique du C, préfèrent utiliser une boucle for :

int factorial(int n) {
    int res=1;
    for(; n; --n) res *= n;
    return res;
}

Ce code fonctionne parfaitement bien. Il a l’unique défaut d’être finalement assez éloigné de la définition mathématique de la factorielle, c’est-à-dire que la plupart des gens le jugeront peu naturel.

Considérons maintenant une implémentation plus naturelle, très proche de la définition apprise sur les bancs d’école. C’est aussi la façon dont la plupart des programmeurs débutants écrivent cette fonction :

int factorial (int n) {
    if (n == 0) return 1;
    else return n * factorial(n - 1);
}

Cette implémentation récursive est assurément très mauvaise. Elle consomme beaucoup de mémoire en pile, donc c’est lent et ça peut aussi se terminer prématurément si on l’appelle avec un paramètre trop grand (stack overflow). La version récursive terminale pallie précisément ce problème :

int factorial (int n, int accu) {
    if (n == 0) return accu;
    else return factorial(n - 1, n * accu);
}

C’est une tail-end recursion car l’appel récursif est le dernier statement. Vous avez certainement remarqué que pour ce faire, j’ai dû introduire un paramètre supplémentaire ; ceci est discuté plus loin.

Comment ça marche ?

Comme vous le savez certainement, lorsque l’on effectue un appel de fonction, la fonction appelante stocke sur la pile d’exécution les paramètres de la fonction appelée et l’adresse de retour. La fonction appelée utilise ses paramètres pour effectuer ses calculs et, à la toute fin, saute à l’adresse précédemment sauvegardée. On revient alors dans le code de la fonction appelante qui dépile avant de continuer…

Sachant cela, il est facile de comprendre qu’un grand nombre d’appels de fonctions imbriqués fait grossir la pile, et que les fonctions récursives représentent un risque important de débordement de pile (stack overflow).

L’optimisation opérée par le compilateur consiste à produire un code qui, lors de l’appel récursif, au lieu d’empiler… et de dépiler plus tard, va remplacer les paramètres actuels et simplement boucler. Ceci est possible dans le cas particulier d’une fonction récursive terminale car il n’y a plus rien à faire dans la fonction appelante.

En fait cette optimisation n’est pas propre aux fonctions récursives. Toute fonction se terminant par un appel de fonction (appel récursif ou pas) est susceptible de profiter de la même optimisation. On parle de sibling call ou tail call.

Un dessin valant mieux que des mots, voici un exemple d’optimisation de tail call sur une machine windows x86 : la méthode foo se termine par un appel à la méthode bar, donc on libère la place réservée pour l’exécution de foo avant d’exécuter bar.

 As soon as foo          While foo        When foo is about to
   is called            is executing        tail call to bar

                      |              |
                      +--------------+
                      |              |
                      |              |
                      | foo's frame  |
                      |              |      |              |
                      |              |      +--------------+
|              |      |              |      |return address|
+--------------+      +--------------+      +--------------+
|return address|      |return address|      |              |
+--------------+      +--------------+      |              |
|              |      |              |      |              |
|  stack args  |      |  stack args  |      |  stack args  |
|    of foo    |      |    of foo    |      |    of bar    |
|              |      |              |      |              |
+--------------+      +--------------+      +--------------+
|              |      |              |      |              |
|              |      |              |      |              |
|              |      |              |      |              |
|   Frame of   |      |   Frame of   |      |   Frame of   |
| foo's caller |      | foo's caller |      | foo's caller |
|              |      |              |      |              |
|              |      |              |      |              |

Ce dessin suggère un algorithme assez grossier pour libérer la pile de foo. En fait le compilateur peut faire quelque chose de très subtile grâce à un calcul de dépendances des variables et expressions. On produit alors un code en tout point semblable à la meilleure des boucles (un décompilateur ne pourra pas faire la différence).

Pour ce faire, le compilateur doit effectuer des analyses compliquées. Ces analyses sont biens connues : factorisation, élimination du code mort, une allocation optimales des registres, l’analyse d’échappement, le calcul de causalité (élimination des synchronisations inutiles, invalidation des caches mémoires), et bien d’autres. Cependant elles font toujours l’objet d’une recherche intense. Le défi est d’être capable d’effectuer une optimisation de qualité dans un temps raisonnable (utilisable dans un JIT) et/ou en consommant très peu de mémoire (carte à puce, téléphone). Mais je me perds dans mes digressions, revenons à notre sujet principal.

A-t-on raison de parler d’optimisation ?

Certains auteurs contestent, à tort, le terme d’« optimisation ». L’argument avancé est qu’un programme qui se termine par une erreur stack overflow n’a visiblement pas le même comportement qu’un programme qui se termine en retournant un résultat (ou qui ne se termine pas).

En effet, pour être correcte une optimisation ne doit pas modifier la sémantique d’un programme (et ce n’est pas toujours simple, cf. la gestion de la sécurité, les interactions avec le ramasse miettes, la gestion des exceptions et des signaux etc.) Mais en algèbre de compilation on considère toujours que la machine cible possède une mémoire infinie. Sans cela, la plupart des optimisations ne pourraient pas être effectuées par le compilateur au prétexte que cela pourrait prévenir une erreur au runtime…

Voici un exemple pour les réfractaires :

a = 1 + 1 + 1 + 1; // ça fait 4

La plupart des compilateurs effectuent les calculs à la compilation quand c’est possible. Pourtant, ce faisant on change effectivement le comportement du programme à l’exécution car il est possible que l’évaluation de cette expression provoque un stack overflow dans certaines conditions.

Donc oui c’est une optimisation ; le terme consacré est Tail Call Optimization (TCO).

Que faire du paramètre supplémentaire ?

Comme je l’ai déjà fait remarquer dans l’exemple de la factorielle, pour écrire une fonction récursive sous forme terminale, il est souvent nécessaire de modifier sa signature en y ajoutant un paramètre.

Que faire de ce paramètre ?

  • On peut le considérer comme une astuce qu’il convient de cacher. Dans notre exemple, le paramètre accu joue le même rôle que la variable de boucle res.
  • On peut aussi le considérer comme un paramètre qui fait sens et qui améliore notre fonction. Ainsi dans notre exemple, le second paramètre accu sert à choisir la valeur du cas de base (factorielle(0)).

En général, on souhaite masquer ce paramètre en trop. On définit alors deux fonctions : la première que l’on cache et qui prend deux paramètres, la seconde qui appelle la première et expose la signature voulue. Pour faire cela, il existe diverses méthodes selon le langage (et les paradigmes) utilisé.

On peut définir la fonction récursive dans la fermeture (closure) de la fonction principale. C’est une technique couramment utilisée dans les langages fonctionnels.

(* Exemple en OCaml *)
let factorielle = function n ->
    let rec __factorielle = function n -> function accu ->
        if n = 0
        then accu
        else __factorielle (n-1) (n*accu)
    in __factorielle n 1
;;

Plus classique, on peut restreindre la portée de la fonction récursive. Ainsi en C, on peut utiliser le mot static pour empêcher une fonction d’être exposée dans l’interface du module.

// Exemple en C
static int __factorielle (int n, int accu) {
    if (n == 0) return accu;
    else return __factorielle(n-1, n*accu);
}
int factorielle (int n) {
    return __factorielle(n, 1);
}

La plupart des langages objets offrent des modificateurs d’accès. En Java, on utilisera les modificateurs public et private :

/* Exemple en Java */
public Mathematiques {
    private static __factorielle(int n, int accu) {
        if (n == 0) return accu;
        else return __factorielle(n-1, n*accu);
    }
    public static factorielle (int n) {
        return __factorielle(n, 1);
    }
}

Certains langages, tels que Python ou C#, permettent de donner une valeur par défaut à un paramètre. Ceci est particulièrement utile dans notre cas.

# Exemple en Python
def factorielle(n, accu=1):
    if n == 0: return accu
    else: return factorielle(n-1, n*accu)

Évidemment il n’y a pas une seule bonne manière de faire. Vous devrez juger quelle est la technique la plus pertinente, la mieux adaptée à votre cas. C# est sans doute le langage qui offre le plus de possibilités.

Est-ce que toutes les fonctions récursives peuvent être écrites sous une forme terminale ?

Oui, toute. Les preuves : les théories de la calculabilité, la logique combinatoire, combinateur de point fixe.

Un second exemple commenté : les nombres de Fibonacci

Considérons ensemble une fonction un peu plus difficile à écrire : la fonction qui calcule les nombres de Fibonacci.

Pour écrire la fonction de Fibonacci sous forme récursive terminale, nous avons besoin d’utiliser non pas un, mais deux paramètres supplémentaires (accu0 et accu1). Ceci est à rapprocher du fait que l’implémentation itérative utilise deux variables de boucle (res0 et res1) et du fait que la définition récursive donne deux cas de base (fibonacci(0) et fibonacci(1)).

# Pour changer, voici du Ruby
# Nombre de Fibonacci, implémentation naïve
def fibonacci_naive(n)
    if n == 0
        0
    elsif n == 1
        1
    else
        fibonacci_naive(n-1) + fibonacci_naive(n-2)
    end
end

# Nombre de Fibonacci, implémentation avec une boucle
def fibonacci_boucle(n)
    res0, res1 = 1, 0 # Pour n'avoir qu'un seul test, on commence au rang -1
    (1..n).each do res0, res1 = res1, res0+res1 end
    res1
end

# Nombre de Fibonacci, implémentation récursive terminal
def fibonacci_recursive(n, res0=1, res1=0)
    if n == 0
        res1
    else
        fibonacci_recursive(n-1, res1, res0+res1)
    end
end

La fonction de fibonacci est particulière à bien des égards. L’implémentation naïve, outre le fait de ne pas être terminale, présente le défaut de nous faire calculer de nombreuses fois les mêmes valeurs (par exemple Fibonacci(4) = Fibonacci(3) + Fibonacci(2) et Fibonacci(3) = Fibonacci(2) + Fibonacci(1), donc nous calculons deux fois Fibonacci(2)…)

Ici, l’implémentation récursive terminale apporte beaucoup plus qu’une bonne gestion de la pile d’exécution. C’est souvent le cas. Ceci ne signifie pas qu’il faille systématiquement utiliser des fonctions récursives terminales (par exemple, je trouve plus lisible d’utiliser une syntaxe for each pour parcourir une collection). Mais je crois que l’étude des fonctions terminales est très formatrice, et qu’elle aide les développeurs à concevoir des algorithmes concis et optimaux.

Est-ce que ça marche pour tous les langages ?

Malheureusement non !

En fait, d’un point de vue technique, cela dépend non pas du langage mais du compilateur. Cependant, on peut remarquer que certains langages, tels que F# et OCaml, définissent ce comportement dans leur spécification.

Pour les langages fonctionnels, ne pas optimiser correctement les appels terminaux est généralement considéré comme un bogue. C’est pleinement justifié car ces langages promeuvent l’utilisation de fonctions récursives. En revanche, la chose est plus discutée pour les autres langages. D’ailleurs, nous allons voir que les plateformes Java et .NET ne sont pas très satisfaisantes sur ce point.

Faisons ensemble un petit tour d’horizon parmi les technologies les plus utilisées.

Langage C

La norme C ne dit rien. Mais tous les bons compilateurs (VS, gcc) proposent une option pour effectuer cette optimisation. Voici un extrait de l’aide de gcc, le fameux compilateur C :

-foptimize-sibling-calls
        Optimize sibling and tail recursive calls. Enabled at levels -O2, -O3, -Os.

Notons au passage que le choix des options de compilation est vraiment très important. Beaucoup de programmeurs ont l’habitude de compiler le programme de release avec l’option « -O2 » et de déboguer avec les options « -O0 -g ». Ce faisant on change le comportement du programme et il est possible de provoquer une erreur stack overflow qui n’était pas l’objet des tests.

Plateforme Java

Java pour les nuls

Une des techniques essentielles de Java est l’utilisation d’un langage intermédiaire appelé bytecode Java. Un programmes Java est transformé (traduit, compilé) par le JDK en bytecode Java qui pourra être exécuté (interprété ou compilé à la volée et exécuté) par toutes les JVMs.

Un bytecode est un langage neutre, facile à parser et à projeter sur les architectures courantes. Le bytecode Java est un langage à pile exposant certaines abstractions caractéristique du paradigme objet tels que les appels de méthodes virtuelles et statiques.

L’optimisations des tail calls en Java

Le JDK peut optimiser les fonctions récursives terminales en les transformant en simple boucle. Mais il est beaucoup plus intéressant d’optimiser tous les appels de méthodes terminaux (tail calls) car c’est un cas plus général qui traite aussi la récursivité croisée et la programmation par continuation. Mais le JDK ne peut pas optimiser les tail calls en général car le bytecode Java n’offre pas les instructions nécessaires (on ne manipule pas directement la pile et on ne peut pas faire de jump n’importe où). Ceci ne peut donc être fait que par la JVM lors du chargement des classes.

Dites moi ce dont vous avez besoin et je vous expliquerai comment vous en passer

The Java Language Specification ne dit rien au sujet des tail recursive calls.

Chaque vendeur de Java VM fait ce qu’il veut. La JVM fournie par Sun, qui est de fait l’implémentation de référence, n’effectue pas cette transformation au grand damne des faiseurs de langages dynamiques fondés sur Java (script Groovy, JRuby, etc.)

public class Main {
    public static void main(String[] args) { main(args); }
}

Si vous exécutez le programme ci-dessus sur une JVM de Sun, il se terminera toujours rapidement et lamentablement par une exception Stack Overflow. Heureusement certaines implémentations de Java gère les appels de méthodes terminaux, c’est le cas notamment de la JVM d’IBM.

Sun ne communique pas beaucoup autour de ce sujet. On peut tout de même voir que ce problème est noté en very very very low priority (Votez pour ce bug !) Tant que que Sun/Oracle ne supportera pas officiellement les tail recursive calls, je crains qu’il ne nous faille renoncer à ce type de programmation en Java.

Voici quelques billets très intéressant sur le sujet : java closures controversy, non java languages on jvm et tail calls in the VM.

Plateforme .NET

Pour les mêmes raisons qu’en Java, l’optimisation des tail recursive calls peut être effectuée aussi bien par le compilateur que par la plateforme d’exécution (CLR). En revanche, l’optimisation des tail calls ne peut être effectuée que par le CLR, au moment du chargement des bibliothèques.

L’optimisation des tail calls étant requis pour traiter correctement la récursivité croisée et, plus généralement, les continuations, choses couramment utilisée dans les langages fonctionnels, la plateforme .NET qui se veut être une plateforme multi-langages et multi-paradigmes, doit optimiser correctement les tail calls.

Voici ce que dit la documentation de CIL (le langage intermédiaire commun utilisé par .NET) :

CIL ECMA 335 spec “use a tail. prefix to specify that the current stack frame should be released before transferring control. If the call would transfer control to a method of higher trust than the original method the stack frame will not be released.”

Malheureusement, le comportement de la plateforme .NET de Microsoft est loin d’être satisfaisant. Pour des raisons qui me sont obscures (ordre des paramètres sur la pile, compatibilité binaire avec du code natif, concurrence avec le GC), la plate-forme .NET ne sait pas, en général, optimiser correctement les tail calls.

Voici une liste des conditions requises pour que le JIT du CLR2 effectue une TCO :
http://blogs.msdn.com/davbr/pages/tail-call-jit-conditions.aspx. J’ai été étonné par certaines de ces limitations qui me semblent mal fondées. Microsoft s’est visiblement montré très défensif dans les deux premières versions du CLR.

Concrêtement les comportements du compilateur et de la VM dépendent de l’architecture de la machine. L’implémentation de la CLR pour x86, au lieu d’une savante manipulation, utilise une fonction magique et affreusement lente qui décale le sommet de pile (cf. le dessin au début de l’article). Par conséquent, la plupart des compilateurs .NET ne produisent jamais le préfixe tail. et les compilateurs de langages fonctionnels tels que F# ou parallelC# transforment les fonctions récursives terminales en boucles. Mais il s’agit plus d’un workaround qu’autre chose. En effet cela ne traite pas les cas de co-récursivités et plus généralement de la correcte optimisation d’un programmation écrit par continuation.

L’implémentation pour x64 est meilleure. En générale, tous les arguments d’une fonction peuvent être stockés dans les registres processeurs car ils sont beaucoup plus nombreux sur cette architecture. Grâce à cela, les performances des tail calls sont acceptables. Par conséquent, sur x64, le CLR effectue des TCO même quand le prefix tail. n’a pas été spécifié dans le code CIL, si les conditions requises sont réunies…

Microsoft a bien conscience du problème. Un effort significatif a été apporté à l’optimisation des tail calls dans la CLR4 sur x86_64. Maintenant les optimisations faciles sont toujours effectives et, pour les rares cas restant, on utilise la fonction magique dont je parlais tantôt afin de toujours tenir compte du prefix .tail dans le code IL.

Oyez, programmeur. F# fonctionne correctement (enfin) sur CLR4/x86_64 !

Remarquez que l’optimisation correcte des tail calls a été l’occasion pour Microsoft d’améliorer son compilateur en supprimant certaines actions inutiles (copie de buffer de values, etc.) Je me répète sûrement : la récursivité aide les développeurs, mêmes les très bons, à concevoir des algorithmes concis et optimaux.

Python

Guido van Rossum, le créateur et leader du langage de programmation Python a publié un billet horrible où il tente d’expliquer pourquoi les TCO sont le mal et ne devraient surtout jamais être effectuées en Python.

Certains des arguments avancés sont tout simplement faux. Vous trouverez quelques arguments bien construits dans les commentaires de cet autre billet.

Python est un langage qui a été pensé pour être facile à utiliser, et non pas pour produire des programmes rapides. En cela je comprends parfaitement que les TCO ne soient pas une priorité, mais il n’y a aucune raison valable pour s’y opposer par principe.

Ruby

La spécification de Ruby ne dit rien au sujet des tail recursive calls.

Yukihiro Matsumoto (le créateur de Ruby) semble être favorable au TCO, mais, il n’a pas souhaité forcer toutes les implémentations de Ruby à le supporter. Voilà une position qui me semble sage ;-) .

YARV, qui est l’interpréteur Ruby officielle, supporte le TCO depuis la version 1.9. Notons que c’est très récent et que l’option, considérée encore instable, est désactivée par défaut (il est nécessaire de commenter une ligne et de recompiler l’interpréteur).

Quant à JRuby et Ruby.NET, ils dépendent évidemment des capacités de la plateforme sous-jacentes..

Ceci signifie que vos programmes Ruby ne devraient pas se fonder sur le TCO, car dans ce cas ils ne seront pas portables sur toutes les Ruby VM.

Bookmark and Share

Java Performance – tips

In my day to day work at FastConnect I am often dealing with java performance that is mandatory in many of our projects. While digging into my code and java language I found some tips that helped us to perform better. I decided to share some of them with you in this article.

Note that these tips are various, some of them are quite extreme and can create a code complication that is not always welcome. Thus they must be used carefully and only when it is really required. Some other on the other way can also help to reach as well as performance an easier readability. Note that as always a good documentation and structure is necessary to make the overall comprehensible.

Limit Object creations

One aspect that can be easily implemented and that can help in frequently called scenarios is to limit the creation of Objects. As you know creating an Object requires first the JVM to allocate space for it and perform all the linking operations and second to manage this object in the garbage collection process, these two operations can have performance impact when dealing with frequent short-lived object creation.See the following example:

package fr.fastconnect.java.performance;

public class ObjectCreationOverheadDemo {
	/* Static fields and methods */
	/**
	 * 10.000.000
	 */
	private final static int ITERATIONS=10000000;

	public static void main(String[] args) {
		// launch a first test with both and no mesurement to not take
		// JVM initialisations in the measurement
		executeWithNewObject();
		executeWithExistingObject();
		// perform test and measure performance
		long start = System.currentTimeMillis();
		executeWithNewObject();
		long duration = System.currentTimeMillis()-start;
		System.out.println("Duration with new object creation : " + duration);
		start = System.currentTimeMillis();
		executeWithExistingObject();
		duration = System.currentTimeMillis()-start;
		System.out.println("Duration with single object       : " + duration);
	}

	private static void executeWithNewObject() {
		for(int i=0;i<ITERATIONS;i++) {
			ObjectCreationOverheadDemo demoObject = new ObjectCreationOverheadDemo();
			demoObject.intValue = i;
			demoObject.longValue = i;
			/*.. Application usage of the object ..*/
		}
	}

	private static void executeWithExistingObject() {
		ObjectCreationOverheadDemo demoObject = new ObjectCreationOverheadDemo();
		for(int i=0;i<ITERATIONS;i++) {
			demoObject.intValue = i;
			demoObject.longValue = i;
			/*.. Application usage of the object ..*/
		}
	}

	/*
	 * Object fields
	 */
	private int intValue;
	private long longValue;
}

Running the test on my machine I got the following output that confirm the impact of object creation.

Duration with new object creation : 174
Duration with single object : 26

Note that in this situation this is small objects and if the impact exist it is in fact very small. Indeed a few millisecond for so many iterations may seems limited. However think on the impact with bigger objects, I’m thinking on the impact on garbage collection mainly.

Working with String:

Java String methods are very fast, however a bad usage of String features can have a very heavy impact on your program performance.

String concatenation

When you are performing multiple string concatenations, try to use a StringBuilder instead of a ‘+’ between the two strings. StringBuilder class is the ultimate optimized solution to append a string to another.

package fr.fastconnect.java.performance;

public class StringConcatenationDemo {
	/* Static fields and methods */
	/**
	 * 10.000
	 */
	private final static int ITERATIONS=10000;
	private final static String strToAppend = "Str To Append";

	public static void main(String[] args) {
		// launch a first test with both and no mesurement
		// to not take JVM initialisations in the measurement
		executeStringPlus();
		executeStringBuilder();
		// perform test and measure performance
		long start = System.currentTimeMillis();
		String strPlusResult = executeStringPlus();
		long duration = System.currentTimeMillis()-start;
		System.out.println("Duration with '+' concatenation : " + duration);
		start = System.currentTimeMillis();
		String strBuilderResult = executeStringBuilder();
		duration = System.currentTimeMillis()-start;
		System.out.println("Duration with StringBuilder     : " + duration);
		System.out.println("String are the same : "+strPlusResult.equals(strBuilderResult));
	}

	private static String executeStringPlus() {
		String resultString="";
		for(int i=0;i<ITERATIONS;i++) {
			resultString += strToAppend;
		}
		return resultString;
	}

	private static String executeStringBuilder() {
		StringBuilder strBuilder = new StringBuilder();
		for(int i=0;i<ITERATIONS;i++) {
			strBuilder.append(strToAppend);
		}
		return strBuilder.toString();
	}
}

Running this example on my machine I got the following output:
Duration with ‘+’ concatenation : 7685
Duration with StringBuilder : 7
String are the same : true

Here again the benefit impact of StringBuilder is clear.

getBytes vs custom implementation

getBytes method in java is quite slow. This is related mainly to char encoding parameters and validations. In certain circonstances where encoding is the same on every machine and doesn’t cause issues, we can use an optimised custom implementation:

package fr.fastconnect.java.performance;

import java.util.Arrays;

public class StringGetBytesDemo {
	/* Static fields and methods */
	/**
	 * 10.000
	 */
	private final static int ITERATIONS=100000;
	private final static String str = "String to get bytes from";

	public static void main(String[] args) {
		// launch a first test with both and no mesurement
		// to not take JVM initialisations in the measurement
		execute();
		executeCustom();
		// perform test and measure performance
		long start = System.currentTimeMillis();
		byte[] bytes = execute();
		long duration = System.currentTimeMillis()-start;
		System.out.println("Duration with getBytes : " + duration);
		start = System.currentTimeMillis();
		byte[] bytesCustom = executeCustom();
		duration = System.currentTimeMillis()-start;
		System.out.println("Duration with custom impl     : " + duration);
		System.out.println("byte arrays are the same : "+Arrays.equals(bytes, bytesCustom));
	}

	private static byte[] execute() {
		for(int i=0;i<ITERATIONS-1;i++) {
			str.getBytes();
		}
		return str.getBytes();
	}

	private static byte[] executeCustom() {
		for(int i=0;i<ITERATIONS-1;i++) {
			char buffer[] = new char[str.length()];
			int length = str.length();
			str.getChars(0, length, buffer, 0);
			byte b[] = new byte[length];
			for (int j = 0; j < length; j++) {
				b[j] = (byte) buffer[j];
			}
		}
		char buffer[] = new char[str.length()];
		int length = str.length();
		str.getChars(0, length, buffer, 0);
		byte b[] = new byte[length];
		for (int j = 0; j < length; j++) {
			b[j] = (byte) buffer[j];
		}
		return b;
	}
}

Note that in many cases the java getBytes and custom method will just produce the exact same byte array. This is something you must check in your tests (providing a switch to use one or the other can also be a good idea).
Duration with getBytes : 74
Duration with custom impl : 15
byte arrays are the same : true

Serialization tips

In many situations serialization can be costly as well in term of size and as well performance. This is linked as IO operations are related to size of data, and serialization’s goal is as you know to be able to send data on an IO channel.
In such situation when performance is a must it may be useful to use a custom serialization.

Use Externalizable

To enable custom serialization for your object, you must implements the Externalizable interface. This interface will provide you 2 methods writeExternal and readExternal that will allow you to deal by yourself with the object serialization.
When using the native java serialisation on your objects, java will therefore use your Externalizable method instead of native serialization process.
Be careful to use this only if the custom serialization has better performance or footprint than the java classic serialization. This is mostly the case on objects that have complex fields and not only primitive.

Create byte array on your own

In some really extreme situations you may want to go even deeper in the performance optimization and create by yourself a byte array that you will use for writing to a network or file stream. (Or basically any stream). Indeed classic java serialization save some class meta-datas. All this overhead can be avoided by creating a custom byte-array. Using custom byte array creation allow also to perform custom batching etc. Basically this is really powerful but has also an impact on code readability. Thus such implementation must be used carefully, be well documented and must use methods with explicit names.

Try to size your data to avoid buffer resizing

When java developer want to create a byte array (serializing data dealing with various buffer…), the most common used class is the ByteArrayOutputStream. this object allow you to write bytes to a stream and to store it in a byte array. The big point with it is that the goal of it is too be able to resize this array anytime if you keep putting data in it. However as explained before the ByteArrayOutputStream can be costly because of ArrayCopy. If you are able to size the data correctly, or if you can send the data by chunk from a defined size, I encourage you to use the ByteBuffer class that will perform a lot better! It will bring however the constraint of not resizing the byte array.

When working directly with more deep layer native networking etc., it is even possible to create the ByteBuffer in the native heap! This will be even more faster to just put data in it as well as to send to network layer but you won’t be able to get any backing byte array back in you java application!! Also this kind of buffer use Native memory and will not be controlled by the heap so be even more careful regarding memory leaks ;)

Multithreading:

Avoid thread common variables and synchronization of resources

When you can do it try to avoid sharing any ‘thread common variables’. When you use a buffer for example try to create one per thread, even if you have to merge them at the end. To do so you can ether use ThreadLocal variables or even better just use full different instances (when you use spring remember that if singleton is the default object scope, using prototypes can be sometimes a better approach). Even if this sounds basic, keep it in mind: achieving a performing multithreaded implementation can be done only with the very minimum synchronization points.

Queue instead of synchronize

When your process can be done asyncronously and you have a synchronization point then queue your process (java.util.concurrent.BlockingQueue) and use a single consumer. This will allow that you don’t block all other thread because of this synchronization point and can help to make your application perform better. If you are afraid that the queue can grow too much don’t worry: BlockingQueue can limit the size of the queue. When this size will be reached then you will pay for the synchronization… Anyway in many situation this can help ;)

Use optimized concurrent classes

Use the java concurrent package that contains many Collections and tools that contains high performance implementations for concurrency scenario. This package contains lot of very useful classes that will perform a lot better than other implementations or custom basic synchronized.

Links

http://java.sun.com/developer/technicalArticles/Programming/Performance/

This list is of course not exhaustive and I invite each of you to comment the article and add your tips ;)

Bookmark and Share

How to dump JVM IBM heap and open it for analysis in MAT

Most of the clients I’m working with are using SUN JVM. However some of them are using IBM VM. As you know, even if the same code can be launched on both many things vary from the one to another, VM options, Garbage collection tuning etc…

I worked recently on solving a memory leak issue and I’ve been confronted to the following straightforward problem: How to be able to get a heap dump that I can analyse with the tools I know well, and that I consider as the best in Open-Source tools. For dump analysis I’m referring to MAT (Memory Analyser Tool from SAP hosted by Eclipse foundation).

First Step: How to get a dump

The best way to get a dump for analysis with IBM VM is in fact not to get a “heap dump” but a “system dump”. This is a particularity from IBM VM. Basically a heap dump will not allow you to access fields names and values in the analysis step (using MAT). This means that you will not be able to benefits from the power of OQL queries.

In order to get a dump use the following options:

-Xdump:system:events=user,request=exclusive+prepwalk

You can now export a system dump by launching a kill signal to the application (kill -3 under linux/unix).

I also encourage you to use dumping on OutOfMemory error that is very useful in most of scenario!

-Xdump:system:events=systhrow,filter=java/lang/OutOfMemoryError,request=exclusive+prepwalk

Note that the -Xdump is very powerfull and you can really customize the option to launch a dump on almost any event in your application. Read more here http://publib.boulder.ibm.com/infocenter/javasdk/v6r0/index.jsp?topic=/com.ibm.java.doc.diagnostics.60/html/dumpagents_syntax.html.
System dumps will produce a file named core.(date).(time).(process id).dmp

This file will not be usable as it is for analysis, you must first transform it using the IBM JVM jextract command on it. This will generate a zip file that you’ll be able to process.

Open the dump in MAT for analysis

Now you can import the zip file generated from the first step into MAT and analyse your dump!

I hope this article will help you if you work with IBM VM and solve memory issues some day.

Bookmark and Share

Soirée DDD avec Eric Evans

Les followers de @parisjug étaient au courant, Eric Evans (auteur de domain driven design) était de passage à Paris en ce début de semaine. Grâce à leur réactivité, les membres du Jug ont pu nous organiser une petite présentation ce lundi. (Merci au passage à l’Epita qui a accueilli l’évènement).

Malgré l’annonce tardive, la blogosphère Java à bien réagi et l’amphi était plein, environ 110 personnes avaient fait le déplacement.

La présentation d’Eric s’articulait sur les contextes d’utilisation du DDD ainsi que ses apports en comparaison aux techniques plus classiques.

Pour résumer en quelques lignes : Pour les applications dont la complexité inhérente est dûe à la complexité du domaine, l’approche DDD permet, en recentrant les développeurs sur le métier, de collaborer avec des experts du domaine afin de construire un logiciel flexible répondant à leur problématique.

On retiendra donc en points clés de cette présentation :

  • L’unicité du langage : les développeurs et les experts métiers doivent être capables d’exprimer les mêmes concepts avec le même vocabulaire. Ceci afin de faciliter la communication et de permettre le partage de connaissances. Cette unicité doit se retrouver aussi bien dans les phases de design que dans l’implémentation.
  • Le besoin pour les équipes de développement de travailler en étroite collaboration avec les experts du domaine (ce qui permettra justement la construction de ce langage commun et l’échange d’information)
  • Le développement itératif, encore une fois, le besoin de communication entre les équipes de dev et le métier nécessite un processus itératif. Seules les itérations permettent de capitaliser sur les connaissances acquises précédemment.
  • Un contexte d’application clair et nettement défini. La nécessité d’un langage unique et d’un modèle adapté aux problématiques métiers exclu de facto les possibilités d’universalité de ce modèle. Un modèle n’étant donc qu’une représentation partielle adaptée aux besoins, il faudra définir les clairement les limites d’application de ce modèle afin de pouvoir s’en affranchir dans d’autres contextes.

La soirée s’est conclue par la traditionnelle troisième mi-temps, délocalisée cette fois à la porte d’Italie !

Prochain évènement de l’agenda : Soirée ALT.NET

Bookmark and Share

Présentation Data Grid au Paris JUG

Demain soir à 19h30 se déroule le traditionnel Paris JUG, événement réunissant la communauté Java parisienne autour d’un certain nombre de sujets d’actualité. Je précise pour les non initiés ;-)
Il est vrai que d’ordinaire nous ne postons jamais au sujet de cet événement car d’autres confrères le font très bien.
Cependant, il me semblait important de mentionner la présentation Data Grid qui me semble très intéressante et d’actualité dans un certain nombre de projets.

Qu’est ce que le Data Grid ?

Tous ceux qui sont confrontés à des problématiques de temps d’accès aux données ou de traitements massifs de ces données recherchent des solutions alternatives aux bases de données relationnelles.
Les Data Grid en mémoire permettent de clusteriser la mémoire d’un parc hétérogène de machines tout en fournissant un accès simple (clef valeur, ?QL, template d’objet) et transactionnel, à cet espace mémoire virtualisé.
Le stockage des données en mémoire est fiabilisé par un certain nombre de mécanismes et le Data Grid fournit les indispensables mécanismes d’intégration au SI dont, notamment, les bases de données existantes.
Lorsqu’on revient aux fondamentaux; j’aime considérer les technologies en identifiant leur impact sur les éléments matériels sous-jacents (CPU, mémoire, disque, réseau); on se rend compte que les technologies Data Grid fournissent une réponse très adaptée dans beaucoup de situations car les temps d’accès et la bande passante de la mémoire sont infiniment supérieurs à ce que peuvent proposer les disques.
Évidemment, on peut toujours payer très cher pour essayer d’atteindre les mêmes performances que la mémoire avec des disques spécialisés, mais le veut on ?
Oui me diront certains, ça rassure ! Mais c’est un autre débat et puis ces temps-ci, en période de crise, ça fait pas très classe ;-)

La base de données change de rôle

En tant que partenaire GigaSpaces, un des leaders de ce domaine, nous voyons de plus en plus d’entreprises de toute taille qui démarrent des études afin de choisir le Data Grid qui permettra à leurs projets stratégiques de gérer les problématiques de stockage haute-performance et de traitements de données.
Depuis un peu plus de 3 ans environ, une grosse partie de notre activité a été de concevoir avec nos clients, et mettre en production, des systèmes stratégiques fondés sur les technologies Data Grid. Non seulement ça marche, mais les performances sont au rendez vous :
- plusieurs dizaine de milliers d’appels par seconde
- des temps de réponse inférieurs à la milliseconde sur des réseaux classiques 1GB
tout çà sur une “lame” d’entrée de gamme à quelques milliers d’euros pièce.

Lorsqu’on voit la prolifération d’offres alternatives aux bases de données relationnelles, il est désormais clair que le rôle de la base de données est en train de changer (ou a déjà changé pour certains) dans le système d’informations.
La base de données a imposé son hégémonie dans nos systèmes d’information depuis des années et a fait la richesse de certains acteurs du secteur, mais il est désormais acquis que la base de données ne peut pas monter en charge sans faire la richesse des vendeurs de matériel.
D’ailleurs, curieusement, dans certains cas, ce sont les mêmes ;-)

Le Data Grid s’est donc imposé avec le temps; projet après projet, secteur d’activité après secteur d’activité comme la solution alternative à la base de données, notamment lorsque celle ci se trouve sur le chemin critique des transactions.
Des pionniers de ce secteur comme GigaSpaces martèlent ce message depuis une dizaine d’années maintenant.
D’autres utilisateurs et fournisseurs d’infrastructures massivement distribuées ont utilisé un certain nombre de concepts  d’informatique distribuée sous-jacents aux technologies Data Grid dans leurs infrastructures internes.
Je pense notamment à Google, Amazon, Facebook qui n’ont pas eu le choix, pour des raisons économiques que de créer des alternatives pour stocker et exploiter de manière efficace leurs données.
Comme il faut rendre à César ce qui appartient à César on peut même dire que ces acteurs incontournables du Web poussent à leurs limites les technologies traditionnelles et dans certains cas poussent (ou tirent comme vous voulez) l’innovation. Le concept MapReduce a été popularisé par Google par exemple.

Le Data Grid, la boite à outils pour le traitement massif de données

Lorsqu’il m’arrive de présenter GigaSpaces, je fais souvent l’analogie entre GigaSpaces et les concepts sur lesquels s’appuient ces énormes infrastructures en indiquant que GigaSpaces peut être vu comme une technologie permettant de productiser, et rendre accessible de manière simple un ensemble de concepts d’informatique distribuée.
Cela nous permet à nous, architectes et développeurs, d’avoir les outils permettant d’assurer la performance et la scalabilité de nos applications distribuées.
D’ailleurs à ce propos, je voudrais mentionner cet article qui explique bien un certain nombre de principes de l’informatique distribuée : http://www.hfadeel.com/Blog/Files/ArtofDistributed.pdf tels que les concepts de Grid Computing, de Master Worker ou de MapReduce.

Au delà de la gestion des données en mémoire, de manière fiabilisée et très performante, le Data Grid permet de paralléliser les traitements sur les données en permettant l’implémentation de patterns tels que Master Worker ou Map Reduce.

Les capacités du Data Grid sont multiples, les technologies Data Grid sont matures (car d’autres ont débuggé pour vous, vous savez ce que c’est ;-) ), et les cas d’utilisation foisonnent, tous secteur d’activités confondus.

Voilà, j’espère que je vous aurai donné envie de venir assister à cette présentation Data Grid si vous n’étiez déjà convaincus, et je serai présent ainsi qu’un certain nombre de nos consultants (dont Jean-Michel qui participe à cette présentation) pour discuter de tout çà avec vous.

Pour venir : http://www.jugevents.org/jugevents/event/16041
Les détails de la présentation : http://www.parisjug.org/xwiki/bin/view/Meeting/20090512

Bookmark and Share

Java soft-references – usage consequences on a memory based architecture (data grid/caching, cloud)

My previous article on java references introduced the java referencing model with the nice features that it can include. One of them the soft-reference was as I explained very useful for cache and buffers in a VM.

In this article I will discuss the particular effect that using soft-references can have on a java cloud or data-grid. We will particularly focus on GigaSpaces as it is the currently more active solution as a Cloud-Computing end-to-end application server and build in java.

When a developer uses soft reference, he allows the JVM to take control on a part of the heap and manage it just the way she likes. The developer in that case really has no control at all. Even if the Garbage Collection cycles are controlled by JVM the developer can try to invoke GC (unless the -XX:+DisableExplicitGC is on but we really not consider this as a good practise). As all of us know calling an explicit GC is not a hundred percent guaranty of having a real full GC cycle but certainly help to reduce the heap in different scenarios. However this will for sure have no impact at all on the soft-reference usage.

So “why can this be bad?” After all the goal of soft-reference is too be a cache, use the heap but still guarantee the security of the JVM (no risk of OutOfMemoryException). Basically if there is no direct impact on most of application we can wonder what is the impact of the soft-reference on heap monitoring. Indeed the objects held by soft-reference are here but are not consuming heap in a “risky way”. Basically they cannot be considered as responsible for any memory leak or even “real usage”.

However a monitoring of the heap may make people think that the heap is really used and that basically they have a risk for their application. If people can be educated and can find out that memory is related to soft-reference, there is no statistics in the JVM that clearly shows it.

The problem is even more relevant when an application uses a self-monitoring to take actions at given SLA. This is basically what will and should happen in many cases on a java cloud. We can this way enter in SLA being triggered without any good reason.

This is why soft-reference usage must be used with caution in those environments.

If we look at the example of GigaSpaces:

- As a cloud provider GigaSpaces help you to monitor and to define SLAs based on the heap and memory usage. As we already saw this can be really dangerous (depending on your SLA action) if many soft references are used. We can run in scenarios where some part of your application will not be relocated on a VM because of these soft-references. – I want to really emphasis that such scenarios will mostly be bad usage of soft-reference in the application code or sometimes some really extreme situation that I will detail later.

- As a data-grid vendor, GigaSpaces has a very nice memory protection feature that allows the Spaces to protect themselves from OutOfMemory by cancelling write operations performed on it. These protections are based on memory SLAs (see http://www.gigaspaces.com/wiki/display/XAP66/Memory+Management+Facility#MemoryManagementFacility-MemoryUsage) and implies the same impact when the memory is consumed by soft-references.

Again the scenarios that will push to run in such situations are rare and quite extreme. But let details one of them that can happen with GigaSpaces.

The GigaSpaces software uses soft-reference in his communication protocol LRMI to manage buffers. In most situations this will never cause any high heap consumption. However when many client connect to the space to request concurrently big object or many small objects we may run in a situation where many buffers are created and will consume memory. This kind of scenario may happen typically in Grid Computing scenarios, when the whole Grid (we encounter mostly DataSynapse or Platform systems) tries to connect and perform request on a single data-grid node.

The question that comes next is: “so what can I do in such a case?”

The truth is that there is no easy answer to this question. The parameter -XX:SoftRefLRUPolicyMSPerMB help to tune the soft-reference garbaging in SUN JVM. However this parameter is not part of the java specification. It does indeed not exist in other JVM.

I will therefore recommend using in such use-case a SUN JVM to be able to perform the tuning that my application will require.

I don’t know if any of you have been involved in similar situations, and I wonder how you managed to handle this.

Bookmark and Share

Java references

This article will discuss the different java referencing system, benefits and consequences of such usage. Many java developer doesn’t really know about them and may not understand JVM behaviour when interacting with third-party that use them.

Strong-reference

The ‘classic’ referencing model is the strong-reference.

//create a strong reference
Integer myInteger = new Integer(0);

In that case the developer manage a Strong reference to an Integer.
As long as the developer keeps a reference to this integer it is not available for garbage collection. Once the object is unreferenced (by assigning null to it or when the reference goes out of scope of the execution flow) the object becomes available for garbage collection using the classical garbage collection cycles.

Java 1.2 and following new references

Starting from java 1.2, 3 new different implementations of references have been introduced. They have, as it is detailed later, a close Relationship to the way they will be managed and garbaged.

  • WeakReference
  • PhantomReference
  • SoftReference

All of them extends from a main class Reference and are part of the java.lang.ref package (see http://java.sun.com/javase/6/docs/api/java/lang/ref/package-summary.html)

WeakReference

A WeakReference is an Object that holds a reference to an Object but do not prevent him for being garbaged. Back to the previous example, to get a WeakReference to the previously mentioned integer, a java developer will use the following:

Integer   myInteger = new Integer(0);
// create a weak reference to the previous integer
WeakReference<Integer> weakReference = new WeakReference<Integer>(myInteger);

In such a case the component that will manage weakReference will have an access to myInteger using

Integer integerOtherRef = weakReference.get();

If a StrongReference exists to your referenced object somewhere in your code then the get method will always return a reference to the object. If the only reference to the object is the WeakReference that you have, the object is electable for garbage collection. This means that as long as no garbage collection occurs the get method will return a reference to the object to you. However as soon as a garbage collection occurs, the object will be removed from the heap and the get method will return null.

Usage

The most interesting usage of weakreference is the ability to link an object to another that we don’t control the life-cycle by ourselves without creating any memory leak risk.
For example a part of code that you don’t manage uses different foo objects. The third-party code is responsible for managing the foo life-cycles: creates some of them and de-reference them. You can get reference to foo but there is no event that will tell you when a foo is de-referenced.
In that case using a weakreference is a very good practice. This way you won’t have any leak by keeping references to existing objects. A common usage of WeakReference is performed when having a map of object that you don’t manage as key to something that is your responsibility. In such a case you want to have the hash-map value de-referenced just when the key (object you don’t manage) is. The WeakHashMap does such a thing.

PhantomReference

To make it short, a PhantomReference is a WeakReference that have a get method that always return null.

Usage

At first glance it seems to not have any usage: why would I like to keep a reference to an object that I cannot event retrieve ?
In fact PhantomReference goal is to be able to track an object garbaging. This is where I need to introduce the ReferenceQueue.
The ReferenceQueue is a queue which contains reference objects that are appended by the garbage collector after the appropriate reachability changes are detected.
When you give a ReferenceQueue to a PhantomReference or WeakReference constructor, the Reference will be appended to your queue by the garbage collector when it’s available for garbage.

Integer myInteger = new Integer(0);
// create a reference queue to retrieve object ready to be garbaged
ReferenceQueue<Integer> referenceQueue = new ReferenceQueue<Integer>();
// create a phantom reference that will be associated to the reference queue
PhantomReference<Integer> phantomReference = new PhantomReference<Integer>(myInteger, referenceQueue);

The interest of the PhantomReference therefore is to get an indication by polling the queue that a given Object is going to be garbaged.

SoftReference

Last reference type is the SoftReference. SoftReference are reference on objects that are not part to the classical garbage collection cycles. An object that has a SoftReference only will not be garbaged even if there is no more strong-reference to it. The JVM will decide by itself whenever the object must be garbaged or not. Basically the behaviour of such reference depends on the heap that is available in the JVM.

Usage

A soft-reference is really good for caching purpose. Indeed JVM makes sure that it will not cause any trouble related to heap usage (no OutOfMemory are possible because of SoftReference usage) and that JVM will try to keep soft-reference as much at it can be.
Basically SoftReferences is ideal for flexible and secure caching in a JVM.

// create a strong reference
Integer myInteger = new Integer(0);
// create a soft reference
SoftReference<Integer> softReference = new SoftReference<Integer>(myInteger);
// unreference the strong reference: the JVM is now managing the life-cycle of the   soft-reference
myInteger = null;

Sources, links and related reading

Java GC tuning official documentation and JavaDoc:

http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html#1.1.Other%20Considerations%7Coutline

http://java.sun.com/javase/6/docs/api/java/lang/ref/package-summary.html

Related blogs:

http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html

http://www.artima.com/insidejvm/ed2/gc17.html

Related projects:

Even if I didn’t really tested or looked deeply at it I found this cache based on weak-reference implementation that is a good illustration to the capabilities of alternate Referencing capabilities of java.

http://rcache.sourceforge.net/

Bookmark and Share

JavaCampParis

Pour ce qui ne connaisse pas, JavaCampParis est un barcamp orienté Java. C’est donc une (non-)conférence, sous la forme d’ateliers/tables rondes, où le contenu des discussions est fourni par les participants.

Et bonne nouvelle, pour sa 3e édition, le JavaCampParis déménage et s’agrandit !

Après les deux premières éditions sous le format deux sessions d’une heure organisées dans les locaux d’Octo, on passe cette fois-ci à la vitesse supérieure. En effet, les éditions précédentes ont souffert d’un timing un peu court (temps de mise en place, choix des sujets, etc).

Donc rendez-vous donc samedi 31 janvier dans les locaux de Sun pour quatre sessions d’une heure.

Pour les inscriptions et tous les détails ça se passe ici.

Bookmark and Share