Toute variable de type objet est une référence vers une instance de classe allouée en mémoire sur le tas.
La référence particulière codice_1 pointe vers une instance non allouée, et constitue la valeur par défaut des références.
Il faut donc allouer une instance et l'assigner à une variable de type référence avant de pouvoir utiliser un objet et ses membres par l'opérateur de déréférencement "point" : codice_2.
L'opérateur de déréférencement sur une référence nulle déclenche une exception de type codice_3.
MaClasse objet = null;
objet.toString(); // ---> NullPointerException
Un tableau est également un objet, pouvant stocker un nombre fixe d'éléments spécifié à l'instruction codice_4.
int[] tableau = null;
tableau[0] = 1; // ---> NullPointerException
L'instruction "new".
L'instruction codice_4 permet d'instancier une classe en utilisant l'un des constructeurs de la classe.
Par exemple pour créer un objet de type "MaClasse", on écrit :
MaClasse cl = new MaClasse("hello");
Cette instruction sert également à allouer les tableaux en spécifiant le nombre d'éléments entre crochets après leur type.
int[] entiers = new int[15]; // Alloue un tableau de 15 entiers initialisés à 0.
MaClasse[] mes_objets = new MaClasse[20]; // Alloue un tableau de 20 références de type MaClasse initialisées à null.
Les constructeurs.
Un constructeur est une méthode particulière de la classe appelée lors de la création d'une instance de la classe.
Son rôle est d'initialiser les membres de l'objet juste créé.
Un constructeur a le même nom que sa classe et n'a pas de valeur de retour.
Dans l'exemple suivant la classe "MaClasse" dispose de deux constructeurs, l'un ne prenant aucun paramètre et l'autre prenant un paramètre de type "String" :
public class MaClasse
 // Attributs
 private String name;
 // Constructeurs
 public MaClasse()
 name = "defaut";
 public MaClasse(String s)
 name = s;
Toute classe possède au moins un constructeur.
Cependant, il n'est pas obligatoire de déclarer un constructeur pour une classe.
En effet, si aucun constructeur n'est déclaré dans une classe, un constructeur sans paramètre est ajouté de manière implicite.
Celui-ci ne fait rien, en apparence.
Un constructeur d'objet en appelle toujours un autre :
Ces deux derniers points ne sont pas applicables à la classe codice_8 racine de toutes les autres classes.
Le code ci-dessous est strictement équivalent à l'exemple précédent, avec les appels implicites mis explicitement
public class MaClasse extends Object /* Implicite */
 // Attributs
 private String name;
 // Constructeurs
 public MaClasse()
 super(); /* Implicite */
 name = "defaut";
 public MaClasse(String s)
 super(); /* Implicite */
 name = s;
Quand une classe a plusieurs constructeurs qui définissent des valeurs par défaut, il vaut mieux que chaque constructeur appelle un autre constructeur de la classe.
Cela permet de faciliter la maintenance en centralisant le comportement de construction et la définition des valeurs par défaut en un seul endroit du code.
public class MaClasse
 // Attributs
 private String name;
 private String description;
 // Constructeurs
 public MaClasse()
 this("Pas de nom");
 public MaClasse(String name)
 this(name, "Pas de description");
 public MaClasse(String name, String description)
 // super(); /* Implicite */
 this.name = name;
 this.description = description;
Initialisation d'un objet.
L'appel à l'instruction codice_4 crée un nouvel objet de la classe spécifiée initialisé de la façon suivante :
Le code de chaque constructeur codice_13 est constitué de plusieurs parties concaténées :
Exemple.
public class MaClasse extends ClasseDeBase
 // Attributs : un nom et 4 lignes de description
 private String name;
 private DescriptionLine[] description = new String[4]; // (1)
 description[0] = new DescriptionLine("Exemple d'objet"); // (2)
 private DescriptionLine firstline = description[0]; // (3)
 // Constructeur
 public MaClasse(String name)
 super("Exemple"); // (0)
 this.name = name; // (4)
Le code du constructeur codice_17 contient alors cette séquence générée par le compilateur :
super("Exemple"); // (0)
description = new String[4]; // (1)
description[0] = new DescriptionLine("Exemple d'objet"); // (2)
firstline = description[0]; // (3)
this.name = name; // (4)
Les lignes d'initialisation (de (1) à (3) pour l'exemple précédent) sont donc insérées juste après l'appel au constructeur de la classe de base, dans tous les constructeurs de la classe.
L'ordre des initialisations est très important.
Dans l'exemple précédent, si les lignes (1) et (3) sont interverties, une exception de type codice_3 est lancée lors de la construction de l'objet.
Le bloc d’initialisation ne peut pas envoyer explicitement une exception, même s'il s'agit d'une sous-classe de codice_19.
Dans le cas contraire, le compilateur retourne l'erreur codice_20.
public class MaClasseNonImplementee extends ClasseDeBase
 {
 throw new RuntimeException("Non implémentée");
 // Erreur à la compilation : Initializer does not complete normally
Par contre le constructeur peut en lancer :
public class MaClasseNonImplementee extends ClasseDeBase
 public MaClasseNonImplementee()
 throw new RuntimeException("Non implémentée");
Initialisation d'une classe.
Avant la première instanciation, la classe est initialisée par la méthode statique spéciale nommée codice_21.
Le code de cette méthode est construit par le compilateur en concaténant les instructions d'initialisations des membres statiques et des blocs de code précédés du mot codice_16.
Il s'agit donc du même principe que les constructeurs codice_13 vu dans la section précédente mais appliqué aux membres statiques de la classe.
Toutefois aucun code de constructeur n'est ajouté à la fin (pas de constructeur statique), et le code appelle la méthode statique spéciale codice_21 de la classe de base plutôt qu'un constructeur.
Nettoyage.
Ramasse-miettes (Garbage Collector).
Le ramasse-miettes garde un compteur du nombre de références pour chaque objet.
Dès qu'un objet n'est plus référencé, celui-ci est marqué.
Lorsque le ramasse-miettes s'exécute (en général quand l'application ne fait rien), les objets marqués sont libérés.
Son exécution se produit toujours à un moment qui ne peut être déterminé à l'avance.
Il s'exécute lors des évènements suivants :
Il est donc recommandé de libérer toute référence (affecter codice_1) à des objets encombrants (tableaux de grande taille, collection d'objets, ...) dès que possible, ou au plus tard juste avant l'allocation d'une grande quantité de mémoire.
Exemple : Pour le code suivant, il faut octets disponibles, le ramasse-miettes ne pouvant rien libérer durant l'allocation du deuxième tableau.
byte[] buffer = new byte[16384];
// -> 1. allocation de 16384 octets
// 2. affecter la référence à la variable buffer
buffer = new byte[32768];
// -> 1. allocation de 32768 octets
// 2. affecter la référence à la variable buffer
Une fois le code corrigé, il ne faut plus que octets disponibles, le ramasse-miettes pouvant libérer le premier tableau avant d'allouer le deuxième.
byte[] buffer = new byte[16384];
// -> 1. allocation de 16384 octets
// 2. affecter la référence à la variable buffer
buffer = null; // Le ramasse-miettes peut libérer les 16384 octets du tableau si besoin.
buffer = new byte[32768];
// -> 1. allocation de 32768 octets
// 2. affecter la référence à la variable buffer
Mettre à codice_1 les références devenues inutiles au plus tôt permet donc de réduire la quantité de mémoire libre nécessaire à l'allocation de tableaux ou d'objets.
Finalisation.
Lors de la libération des objets par le ramasse-miettes, celui-ci appelle la méthode codice_27 afin que l'objet libère les ressources qu'il utilise.
Cette méthode peut être redéfinie afin de libérer des ressources système non Java.
Dans ce cas, la méthode doit se terminer en appelant la méthode de la classe parente : 
super.finalize();
Comme l'exécution du ramasse-miettes se produit toujours à un moment qui ne peut être déterminé à l'avance, l'appel à cette méthode par le ramasse-miettes n'est pas garanti.
C'est pourquoi cette méthode est rarement implémentée afin d'éviter des effets de bords.
Représentation des objets dans la mémoire.
L'instruction codice_4 alloue la mémoire sur le tas pour les objets et les tableaux.
La création d'un objet de classe C qui hérite de la classe B, elle-même héritant de la classe A, se déroule à peu près de la manière suivante :
La création d'un tableau avec l'instruction codice_4 se déroule de la même façon (sachant que tout type de tableau hérite de la classe codice_8), en allouant la place nécessaire pour le type des éléments multipliée par le nombre d'éléments voulu.
Le tas et la pile.
Le tas est la zone mémoire où sont alloués les éléments de tableaux et les membres des objets.
C'est une zone mémoire très grande, partagée par tous les processus légers (threads) de l'application Java.
C'est pourquoi l'accès à un même objet ou tableau par plusieurs threads nécessite des moyens de synchronisation.
La pile est une zone mémoire locale limitée, alloué pour chaque processus léger (thread).
Cette zone sert de stockage temporaire aux appels de méthodes pour y stocker la valeur des arguments, des variables locales, et de manière interne la valeur de retour.
<syntaxhighlight lang="java">
public class Test
 int a;
 int b = 50;
 String c;
 String d = "test";
 // Code inclus dans tous les constructeurs :
 c = d.toUpperCase();
 System.out.println("C = "+c);
 public static Object creerTest()
 Object o; // Une référence nulle, allouée sur la pile
 o = new Test(); // Objet alloué sur le tas
 return o; // La méthode retourne la référence à l'objet alloué dans le tas
</syntaxhighlight >