Wednesday, 4 July 2012

Garbage Collection in Java


Definition

In our daily life we come across lot of things we don't need forever (called as garbage) and throw them into trash can. The garbage collector collects those garbage periodically and dispose/recycle the garbage in many different ways.

Similarly in Java, when we are done using an object and are no more useful, we dereference them. All these java garbage have to be collected and claim the memory occupied them so that the memory space can be re-used in future. This is done by Garbage Collector and the technique is called Garbage Collection or Memory Recycling.

Precisely, garbage collection is a technique of reclaiming the memory from the objects that do not have any references.

Why Garbage Collection?

Note: I will be writing GC (short name for Garbage Collector) now onwards.

If you do not free memory when an object is no longer needed, at time you may end up taking all spaces and your program may crash suffering out of memory. We must clean up those objects and reclaim memory periodically to make the program healthy. In addition to this GC also does heap fragmentation. Heap fragmentation occurs during normal program execution. New objects are allocated, and unreferenced objects are freed such that free portions of heap memory are left in between portions occupied by active objects. Requests to allocate new objects may have to be filled by extending the size of the heap even though there is enough total unused space in the existing heap. This will happen if there is not enough contiguous free heap space available where the new object will fit. In the process of freeing unreferenced objects, GC must run finalizers of objects (if any) being freed.

GC relieves the burden of freeing allocated memory. Explicit memory clearance is always risky and tricky. Assigning this task to JVM has many advantages. It can make the programmer more productive and he/she can concentrate on the actual logic rather thinking of memory clean up.

The GC Engineering

There is only one GC runs for a JVM for the whole JVM lifetime and gets started during JVM startup. GC runs as a daemon thread (background thread) and pops up periodically for freeing memory. We can request JVM to call GC by calling System.gc() or gc() method of Runtime class (System.gc() just delegates the call to Runtime). But there is no guarantee that GC will act instantly.

GC Algorithm

Any GC algorithm must do some basic things like detecting unreferenced objects, reclaim the heap space used by them and defragment the free space to make sure we have enough contiguous free space.

Garbage detection is accomplished by defining a set of roots and determining reachability from the roots. An object is reachable if and only if there is at least a reference to the object exists by which the program can access the object. The roots are always accessible by the program. Objects those are reachable from the roots are considered LIVE and those are not reachable are considered GARBAGE. The best example of root set is a stack.

Figure: Reachable and unreachable objects in heap and their references from the Root set of references.

GC algorithm depends on JVM implementation. The JVM can be implemented such that the GC knows the difference between a genuine object reference and a primitive type that appears to be a valid object reference. (One example is an int that, if it were interpreted as a native pointer, would point to an object on the heap.) However, some GCs may choose not to distinguish between genuine object references and look-alikes. Such GCs are called conservative because they may not always free every unreferenced object. Conservative GC trades off an increase in garbage collection speed for occasionally not freeing some actual garbage.

Two basic approaches of distinguishing live objects from garbage are reference counting and tracing.
Reference counting GCs distinguish live objects from garbage objects by keeping a count for each object in the heap. The count keeps track of the number of references to that object.
Tracing GCs actually trace out the graph of references starting with the root nodes. Objects that are encountered during the trace are marked in some way. After the trace is complete, unmarked objects are known to be unreachable and can be garbage collected.

Finalization

In Java, an object may have a finalizer. finalize is a method that the garbage collector must run on the object prior to freeing the object. However, the existence of finalizers complicates the job of GC. finalize method needs to be overridden from Object class. GC gives you the final chance to clean up any objects/resources you may have before it gets garbage collected.

Because of finalizers, the GC must perform some extra steps each time it garbage collects. First, the GC must in some way detect unreferenced objects (call this Pass I). If it has enough time, it may at this point in the garbage collection process finalize all unreferenced objects that declare finalizers. After executing all finalizers, the GC must once again detect unreferenced objects starting with the root nodes (call this Pass II). This step is needed because finalizers can "resurrect" unreferenced objects and make them referenced again. Finally, the GC can free all objects that were found to be unreferenced in both Passes I and II.

To reduce the time it takes to free up some memory, a GC can optionally insert a step between the detection of unreferenced objects having finalizers and the running of those finalizers. Once the GC has performed Pass I and found the unreferenced objects that need to be finalized, it can run a miniature trace starting not with the root nodes but with the objects waiting to be finalized. Any objects that are (1) not reachable from the root nodes (those detected during Pass I) and (2) not reachable from the objects waiting to be finalized cannot be resurrected by any finalizer. These objects can be freed immediately.

Finalizer for an object run at most once in the object lifetime. If an object with a finalizer becomes unreferenced, and its finalizer is run, if that object is resurrected by its own finalizer or some other object's finalizer and later becomes unreferenced again, the GC treat it as an object that has no finalizer.

References



Hope you enjoyed this post on garbage collection. Feel free to comment on this. Thank you.


Monday, 2 July 2012

A closer look at the Class Loader


What is a Class Loader?

Class loader is a part of JRE that loads the java classes into your JVM so that JVM need not bother about reading .class files for the classes needed at runtime.

Why is Class loader needed?

When we talk about Java, every piece of code is written in a .java file. Which is a plain text file. This gets complied by a java compiler and creates one or more .class files which we call as byte code. Some group of these .class files are usually archived using jar mechanism producing a .jar file (rt.jar from core java library is an example of jar file which contains core classes). When JVM needs a class for execution, it cannot open the jar file and find out the correct .class file and use it. Class loader is responsible for loading this class into JVM.

Types of class loaders:

1. Bootstrap Class Loader
This class loader is a native class loader. Hence it may have different implementations for different JVMs. This loader loads the classes from the core java library i.e. jars from $JAVA_HOME/lib directory. So all classes from packages like java.lang, java.io, java.util will be available to your JVM with help this class loader.

2. Extensions Class Loader
This loader loads the classes from the jars available in extensions directories ($JAVA_HOME/lib/ext). This can also load the jars from other directories which are defined in java.ext.dirs system property. It is implemented by sun.misc.Launcher$ExtClassLoader.

3. System Class Loader
This loads classes form java.class.path system property or $CLASSPATH env variable. This is implemented by sun.misc.Launcher$AppClassLoader.

4. Custom Class Loader
A custom class loader is needed when classes from some custom repositories need to be loaded. Any number of custom class loaders can be present in a JVM. The best example is a web application under tomcat might have a bunch of jar files in WEB-INF/lib directory. Tomcat creates custom class loader to load the classes from those jar files.

Deligation of class loaders

Here is the heirarchy of the class loaders: Custom -> System -> Extensions -> Bootstrap

When a class is requested by JVM, first it goes to all custom class loaders (if custom class loader exists). Each custom class loader will try to load the class by itself or delegates to its parent class loader (System class loader). System class loader also will try to load by its own or delegates to its parent. This way the delegation goes till the bootstrap loader and the delegation stops here. Custom class loader is an optional entity. If there is no custom class loader found, the above said process will start from System class loader. If the class is not found by any of the above class loaders, a ClassNotFoundException or NoClassDefFoundError will be thrown.


Class Uniqueness

class a.Test loaded by class loader CL1 and class a.Test loaded by class loader CL2 are not same even though they are in same JVM. Class uniqueness is defined by class name, package name and the class loader.


References



Hope you enjoyed this post on garbage collection. Feel free to comment on this. Thank you.









Monday, 5 October 2009

Chat program using JDK 6 and RMI

This chat application is developed using JDK 6 and Java RMI. This can be used for group-chat and one-to-one chat

To develop this application, we need following files.
  1. ChatServerInt.java - An interface for chat server.
  2. ChatClientInt.java - An interface for chat client.
  3. ChatServer.java - Implementation for chat server program.
  4. ChatClient.java - Implementation for chat client program.
  5. chatclient.policy - Policy to access the remote methods.

ChatServerInt.java
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ChatServerInt extends Remote {
   void connect(String name, ChatClientInt c) throws RemoteException;
   void disconnect(ChatClientInt c) throws RemoteException;
   void broadcast(String name, String s) throws RemoteException;
   void list(ChatClientInt c) throws RemoteException;
   ChatClientInt lookup(String name) throws RemoteException;
}


ChatClientInt.java
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ChatClientInt extends Remote {
   void update(String name, String s) throws RemoteException;
   String getName() throws RemoteException;
}


ChatServer.java
import java.io.PrintStream;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ChatServer implements ChatServerInt {

   private static final long serialVersionUID = 1L;
   private List myclients;
   private List clientNames;
   private static final String name = "server";

   public ChatServer() throws RemoteException {
       myclients = new ArrayList();
       clientNames = new ArrayList();
   }

   public synchronized void disconnect(ChatClientInt c) throws RemoteException {
       myclients.remove(c);
       clientNames.remove(c.getName());
       writeLog(c.getName() + " disconnected at {0}");
       for(ChatClientInt client: myclients) {
           client.update(name, c.getName() + " has left.");
       }
   }

   public synchronized void list(ChatClientInt c) throws RemoteException {
       c.update(name, "Active users: " + clientNames.toString());
   }

   public synchronized ChatClientInt lookup(String name) throws RemoteException {
       ChatClientInt c = null;
       int index = clientNames.indexOf(name);
       if (-1 != index) {
           c = myclients.get(index);
       }
       return c;
   }

   public synchronized void connect(String n, ChatClientInt c) throws RemoteException {
       for(ChatClientInt client: myclients) {
           client.update(name, n + " is joining now...");
       }
       clientNames.add(n);
       myclients.add(c);
       int count = myclients.size();
       StringBuffer wcmMsg = new StringBuffer("Welcome ").append(n).append(", ");
       wcmMsg.append("There ").append((1 == count)? "is " : "are ").append(
           count).append((1 == count)? " user: " : " users: ");
       wcmMsg.append(clientNames.toString());
       c.update(name, wcmMsg.toString());
       writeLog(n + " connected at {0}");
   }

   public synchronized void broadcast(String name, String s) throws RemoteException {
       for(ChatClientInt client: myclients) {
           client.update(name, s);
       }
       writeLog("{0}: " + name + ": " + s);
   }

   public static void main (String[] args) {
       if (1 != args.length) {
           System.out.println("Usage: java ChatServer <server_port>");
           System.out.println("Example: java ChatServer 2001");
           return;
       }
       int port = Integer.parseInt(args[0]);

       try {
           System.setOut(new PrintStream("server.log"));
           ChatServer server = new ChatServer();
           LocateRegistry.getRegistry(port).bind("ChatServer",
               UnicastRemoteObject.exportObject(server, 0));
           writeLog("Server started at {0}, waiting for connections...");
       } catch(Exception e) {
           e.printStackTrace();
       }
   }

   private static void writeLog(String log) {
       System.out.println(MessageFormat.format(log, new Date().toString()));
   }
}


ChatClient.java

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Scanner;

public class ChatClient extends UnicastRemoteObject implements ChatClientInt, Runnable {
   private static final long serialVersionUID = 1L;
   private ChatServerInt server;
   private String name;
   private ChatClientInt friend;
   private static final String LIST = "LIST";
   private static final String QUIT = "QUIT";
   private static final String HELLO = "Hello ";
   public ChatClient(ChatServerInt cs, String name) throws RemoteException {
       this.name = name;
       this.server = cs;
       server.connect(name, this);
   }

   public synchronized void update(String name, String s) throws RemoteException {
       if (! this.name.equals(name)) {
           System.out.println(name + ": " + s);
       }
   }

   public void run() {
       Scanner in=new Scanner(System.in);
       String msg;

       while(true) {
           try {
               msg=in.nextLine();
               msg = msg.trim();
               if (QUIT.equals(msg)) {
                   server.disconnect(this);
                   in.close();
                   System.exit(0);
               } else if (LIST.equals(msg)){
                   server.list(this);
               } else if (msg.startsWith(HELLO) && msg.contains(",")) {
                   String s[] = msg.substring(0, msg.indexOf(",")).split(" ");
                   String user = s[s.length-1].trim();
                   friend = server.lookup(user);
                   if (null != friend) {
                       friend.update(name, msg);
                   } else {
                       server.broadcast(name, msg);
                   }
               } else if (! "".equals(msg)) {
                   server.broadcast(name, msg);
               }
           } catch(Exception e) {
               e.printStackTrace();
           }
       }
   }

   public static void main(String[] args) {
       if (3 != args.length) {
           System.out.println("Usage: java ChatClient <server_ip> <server_port> <user_name>");
           System.out.println("Example: java ChatClient 127.0.0.1 2001 user1");
           return;
       }
       String host = args[0];
       int port = Integer.parseInt(args[1]);
       String name = args[2];

       try {
           Registry registry = LocateRegistry.getRegistry(host, port);
           ChatServerInt server = (ChatServerInt) registry.lookup("ChatServer");
           Thread t = new Thread(new ChatClient(server, name));
           t.start();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

   public String getName() {
       return name;
   }
}


chatclient.policy

grant {
 permission java.security.AllPermission;
};

Compile the source and put the classes into bin.
javac -d ../bin/ *.java

Now run rmiregstry (better to use a port) in a new console and leave it as it is.
rmiregistry 2001

Start the chat server program with the same port number 2001.
java -Djava.security.policy= -Djava.rmi.server.codebase=file:/ ChatServer 2001

Now start as many clients you want...
In case you want run the client from the same machine where chat server is running, use following...
java ChatClient 127.0.0.1 2001 user1

In case of remote machine, you must give the server ip in command-line...
java ChatClient 2001 user2


Key Features
  • When user1 comes in all other available users will get a notification saying "user1 is joining now...".
  • The new user will get a wecome message from server and will get to know how many available users are there and who are they.
  • When a user types some message and press enter, the message will be sent to all the users (except the current user) via server.
  • If user1 wants to chat with a specific user(say user2), he can type "Hello user2, blah blah..." and only user2 will get that message from user1.
  • A user can type "LIST" to know the list of current users.
  • A user can type "QUIT" in case he wants to leave chat.
  • All the group-chat conversations and user connect/disconnect are logged into a log file at server side.