Thursday, 30 April 2015

Cheating with Exceptions - Java 8 Lambdas

Leaving aside the religious debate about Checked vs Runtime exceptions, there are times where due to poorly constructed libraries, dealing with checked examples can drive you insane.

Consider this snippet of code which you might want to write:

public void createTempFileForKey(String key) {
  Map<String, File> tempFiles = new ConcurrentHashMap<>();
  //does not compile because it throws an IOException!!
  tempFiles.computeIfAbsent(key, k -> File.createTempFile(key, ".tmp"));
}

For it to compile you need to catch the exception which leaves you with this code:

public void createTempFileForKey(String key) {
    Map<String, File> tempFiles = new ConcurrentHashMap<>();
    tempFiles.computeIfAbsent(key, k -> {
        try {
            return File.createTempFile(key, ".tmp");
        }catch(IOException e) {
            e.printStackTrace();
            return null;
        }
    });
}

Although this compiles, the IOException has effectively been swallowed.  The user of this method should be informed that an Exception has been thrown.

To address this you could wrap the IOException in a generic RuntimeException as below:

public void createTempFileForKey(String key) throws RuntimeException {
    Map<String, File> tempFiles = new ConcurrentHashMap<>();
    tempFiles.computeIfAbsent(key, k -> {
        try {
            return File.createTempFile(key, ".tmp");
        }catch(IOException e) {
            throw new RuntimeException(e);
        }
    });
}

This code does throw an Exception but not the actual IOException which was intended to be thrown by the code. It's possible that those in favour of RuntimeExceptions only would be happy with this code especially if the solution could be refined to created a customised IORuntimeException.  Nevertheless the way most people code, they would expect their method to be able to throw the checked IOException from the File.createTempFile method.   

The natural way to do this is a little convoluted and looks like this:

public void createTempFileForKey(String key) throws IOException{
        Map<String, File> tempFiles = new ConcurrentHashMap<>();
        try {
            tempFiles.computeIfAbsent(key, k -> {
                try {
                    return File.createTempFile(key, ".tmp");
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
        }catch(RuntimeException e){
            if(e.getCause() instanceof IOException){
                throw (IOException)e.getCause();
            }
        }
}

From inside the lambda, you would have to catch the IOException, wrap it in a RuntimeException and throw that RuntimeException. The lambda would have to catch the RuntimeException unpack and rethrow the IOException. All very ugly indeed!

In an ideal world what we need is to be able to do is to throw the checked exception from within the lambda without having to change the declaration of computeIfAbsent. In other words, to throw a check exception as if it were an runtime exception. But unfortunately Java doesn't let us do that...

That is not unless we cheat! Here are two methods for doing precisely what we want, throwing a checked exception as if it were a runtime exception.

Method 1 - Using generics:

    public static void main(String[] args){
        doThrow(new IOException());
    }

    static void doThrow(Exception e) {
        CheckedException.<RuntimeException> doThrow0(e);
    }

    static <E extends Exception>
      void doThrow0(Exception e) throws E {
          throw (E) e;
    }

Note that we have create and thrown an IOException without it being declared in the main method.

Method 2 - Using Unsafe:

public static void main(String[] args){
        getUnsafe().throwException(new IOException());
    }

    private static Unsafe getUnsafe(){
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            return (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

Again we have managed to throw an IOException without having declared it in the method.

Whichever method you prefer we are now free to write the original code in this way:

public void createTempFileForKey(String key) throws IOException{
        Map<String, File> tempFiles = new ConcurrentHashMap<>();

        tempFiles.computeIfAbsent(key, k -> {
            try {
                return File.createTempFile(key, ".tmp");
            } catch (IOException e) {
                throw doThrow(e);
            }
        });
    }
    
    private RuntimeException doThrow(Exception e){
        getUnsafe().throwException(e);
        return new RuntimeException();
    }

    private static Unsafe getUnsafe(){
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            return (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

The doThrow() method would obviously be encapsulated in some utility class leaving your code in createTempFileForKey() pretty clean.

Lessons Learnt Translating 25k line of C# into Java

For various reasons I've recently completed a project converting a complex financial application from C# to Java. The reasons for the port were for the most part non-technical, rather, it was a strategic move for the business concerned. 

It was an interesting experience and I learnt a few lessons along the way that might be useful to share.

1. Construct language neutral tests over the existing system.

I'll start with perhaps the most important lesson of all. When porting a system, and this could be any port for any reason, there must be criteria to determine whether the port has been successful. The best way to do this is to construct a full set of tests around the original system, that can be 'exported without change' to the new system.  So for example, it's no good having a suite of JUnit tests if you want to move system from Java to a different language that doesn't support JUnit.  I can't stress enough how important it was that the changes to the tests could literally be copied from the old system to the new system without intervention. 

Another problem with JUnit tests is that they are often firmly linked to the existing implementation. Since the implementation is going to be rewritten, the tests are not portable between implementations.

The strategy we chose and which worked extremely well was to use Cucumber tests.  There are bindings for Cucumber in nearly all languages, it is well supported by IDEs (at least by both IntelliJ and Visual Studio) and as a bonus the tests are human readable. In this way you can involve non-technical users in building up the tests in preparation for the port. (As an aside, we had an attempt at getting the users to define the requirements for the new system by documenting everything the old system did and building tests round those requirements, but that unsurprisingly was completely unsuccessful.  It's far better building up test cases based off your existing implementation than trying to invent them for the new system!).

Using Cucumber was a real success and we created a new test every time there was a discrepancy between the systems. By the time we have finished we had around 1000 scenarios and we felt confident that the new system was correct.  It gave us the solid foundations we needed to continue developing the additional features and refactorings in the new system.

2. Try and automate as much of the translation as possible.

When faced with 25k+ lines of C# it's a pretty daunting task to think of hand translating every line into Java. Fortunately there are tools out there that are enormously helpful.  The product we used was from Tangible Software Solutions. For a couple of hundred dollars it saved literally hundreds of man hours of time.  It's not perfect by any means but it will give you the structure of the Java code (partials allow code for classes in C# to be split across more than one file) and make a pretty good attempt of giving you workable Java.  In our case hardly any of the generated code actually compiled but it was a really good head start. My analogy would be to early attempts at OCR. You could scan in a document but when you opened it in an editor you would find red underlinings against many words which had not been recognised correctly. It was a matter of going through all the red underlinings and working out what the word should have been. Much is the same with the code produced by the automated translation, when it was pulled into an IDE there were many compiler errors. Sometimes the automation left in the original C# and said that the translation could not be done automatically.  To its credit the tool always erred on the side of being more conservative, it never made mistakes with the Java it produced, which was important. 

3. Don't rush the translation

After you have run automated translation you will need to go back to the code and fix the compile errors by hand. If I had my time again I would spend 10 times longer making sure that every change I made to the code was absolutely correct.  Since I wasn't an expert in C# I sometimes made assumptions as to how the C# libraries worked.  Those assumptions were not always correct and I sometimes paid a heavy penalty debugging scenarios where, had I been more careful in the original translation there would never have been a problem. It's definitely worth spending time reading through the C# API of the classes you are translating. I found this especially important when using Date and DateTime objects.

It's also worth spending time learning the Visual Studio IDE.  When debugging side by side it will save time in the long run if you know how to use your IDE properly.

4. Use Java 8

Apart from all the obvious reasons to use Java 8 (it's the latest version of Java so why not use it...) the Stream API maps nicely onto C# Linq.  The syntax is a little different, for example Java uses '->' and C# uses '=>', but using the new Java 8 features really helps keeping the code comparable which all helps when debugging further down the line.

5. Be careful of unintended behaviour  

There are certain features of languages that you shouldn't rely on but might work all the same. Let me demonstrate with an example on which I spent far too much time.  The C# code was using a Dictionary which the code generator correctly translated to a HashMap. Both are unordered Maps.  However, even though Dictionary is unordered by contract (there is also an OrderedDictionary) when iterating through the Dictionary it seemed to preserve the insertion order.  This was not the case with HashMap, and since the order of elements was material to the result, we found discrepancies which were hard to debug. The solution was to replace all instances of HashMap with LinkedHashMap which does preserve the order.

6. Don't refactor too early

The code produced from the code generator is not pretty.  In fact it's pretty horrific to look at, breaking nearly every rule regarding naming conventions etc.  It's tempting to tidy up as you go along.  Resist that temptation until all your unit tests have passed.  You can always tidy up later.  Refactoring, even renaming, can introduce bugs especially in code base with which you are, by definition, not familiar. Also you might decide to re-run the code generator somewhere down the line and all your tidying up will at best need to be merged and at worst have been a waste of time.

Conclusion

Translating even a fairly complicated program from C# to Java is not impossible even if you're not that familiar with C#.  Using the correct tools and techniques and critically having reliable and repeatable tests will make all the difference to the success of your project. 

See follow up article describing the performance gains achieved by the code port. 

Monday, 27 April 2015

FileSystemMap: A File System backed Map

As part of a project I'm working on at the moment I've been looking at creating a FileSystemMap. I've started a very small GitHub project here to host the code. 

Essentially this map implementation is will allow the user to interact with a directory on their file system as if it were a java.util.Map. Each entry in the map will be a file in that directory, the key will be the file name and the value will be the contents of the file.

This code builds a FileServiceMap and adds five entries:

Map map = new FileSystemMap("/tmp/filetests");
  map.put("one", "one");
  map.put("two", "two");
  map.put("three", "three");
  map.put("four", "four");
  map.put("five", "five");

This results in a directly structure like this:

/tmp/filetests/
 |----- five
 |----- four
 |----- one
 |----- three
 |----- two

Adding and removing entries will change the files in your directory. Changing the value of the entry will cause the file to be re-written with the new value as its contents. For more examples see the code in testMapMethods.

Additionally the FileSystemMap has been designed for two way interaction. Any programmatic updates to it are reflected on the file system and any updates to the file system will be picked up by the map and fired as events.  

This code registers changes to the file system and prints them out:

Map map = new FileSystemMap("/tmp/filetests");
map.registerForEvents(System.out::println);

This is some example output:

FPMEvent{eventType=NEW, programmatic=true, key='one', value='one'}

The eventType is one of: 

  • NEW - a file has been created
  • UPDATE - a file has been modified
  • DELETE - a file has been deleted
The programmatic flag indicates whether it was the FileSystemMap itself that caused the event to be fired. e.g. if put() is called, a file will be created which in turn will cause an event to fired. To avoid feedback it can be useful to know whether it was an operation on the FileSystemMap that triggered the event.

The key is the name of the file that changed.

The value is the latest value associated with the file that has changed.  Note: this may or may not be the value that actually triggered the change.  For example if there were two very fast changes to the entry it is entirely possible that the value for the first event will pick up the value after the second update has already taken place.

e.g. 

map.put("one", "1");
map.put("one", "2");

could produce this output:

FPMEvent{eventType=NEW, programmatic=true, key='one', value='2'}


The first event (triggered by setting to "one" to '1') is picked up but by the time the program has checked the contents of the file the file has changed to '2'. The second event is then picked up (triggered by setting to "one" to '2') but this is suppressed because the value has not changed.  

Some notes:

  1. Files starting with a '.' are ignored. The reason for this is that many editors (including vi) create temporary files which should not be picked up by the FileServiceMap.
  2. If you look at the code you will notice that the WatchService (since Java7) was used to monitor changes to the file system. It is important to understand that the WatchService is OS specific. In particular it doesn't work that well on Mac OSX. This thread discusses some of the issues.  Updates from the WatchService are slow and and fast flowing events can be dropped. In my testing Ubuntu performed significantly better than MacOSX.  However if you're mainly interested in changes to the file system carried out by hand even Mac OSX will be fine.
  3. The Map only supports Strings.

It goes without saying that this class is designed for its specific utility rather than any sort of performance. 

All contributions to this project are welcome!


Thursday, 16 April 2015

Dealing with Interruptions

I was just watching the VJUG interview with Heinz Kabutz which inspired me to write a post about Interruptions. By the way I would recommend subscribing to the VJUG YouTube channel - very informative indeed.

Heinz is always good value and it's difficult to watch any of his presentations without learning a lot. He raised the topic of how to deal with an InterruptedException and postulated that few Java programmers deal correctly with it.  The best explanation of thread interruptions I've read is contained in my favourite book on Java - Java Concurrency In Practice (p138-144). If you've read these pages you will know how to deal with an InterruptedException correctly :-)

Here's a short summary:

How often have you come across this code:

.......
try {
   Thread.sleep(1000);
} catch(InterruptedException e){
   e.printStackTrace();
}
......

A process is required to sleep for a second but 'annoyingly' has to deal with an InterruptedException.  The developer doesn't really know what to do with this exception so just logs it to the console.

This is very bad practice! If you are sure that your thread will never be interrupted (you are writing this code in a closed system) then you should probably do something like throw an AssertionError in the catch block with a comment that this should never happen.  If it is at all possible that the thread might be interrupted then you need to deal with that interruption correctly.

A thread can be interrupted by calling its interrupt() method.  This will set its interrupt status to true and consequently when you call isInterrupted() will return true. When interrupt() is called certain blocking methods, like Thread.sleep() will throw an InterruptedException. Note that triggering the InterruptedException will set the interrupt status to false.  There is a method on Thread called interrupted() which like isInterrupted() returns the interrupt status of the thread but crucially sets the interrupt status back to false. (interrupted() is a very strangely named method for what it does...)

We can see all this at work in the following example:


To quote Java Concurrency in Practice:

"There is nothing in the API or language specification that ties interruption to any specific cancellation semantics, but in practice, using interruption for anything but cancellation is fragile and difficult to sustain in larger applications."

In other words an interrupt is just a signal. You could theoretically use the interrupt mechanism to instruct the thread to do anything you wanted, perhaps to take do action A instead of B - but we are counselled against it.

.......
try {
   Thread.sleep(1000);
} catch(InterruptedException e){
   actionA();
   return;
}
actionB();
......   

So what is the correct way to deal with an interrupt.  Well that depends a bit on your code. Let's assume we are using the interrupt 'correctly' as a cancellation and your code expects a cancellation to occur (this should be specified in the documentation) then your code should cancel its actions in a controlled manner.  Just because an exception is thrown does not means you have to exit in haste leaving a trial of mess behind you. Because you have dealt with the interrupt there is no need to restore the interrupt status on the thread. 

If you do not expect an interrupt then you should handle the interrupt gracefully (maybe finish what you are doing) and then restore the interruption on the thread for some code higher up the stack to deal with. Remember once the exception has been thrown the interrupt status is set to false. Here's the way (code taken from the book) as to how it should be done:

Tuesday, 7 April 2015

Java 8 Lambdas in One Line

If you understand this line, or better still can write this code you can pretty much say that you have understood the essence of Java 8 Lambdas. Certainly in as much as they can be used with collections.

I found this in a recent presentation by Peter Lawrey.  (Definitely worth watching the whole presentation when you have a spare hour.)

Anyway the task was to find the 20 most frequent words in a file:

As you can see, with Java 8 this can actually be done in a single (albeit rather long) line. 

If you're not used to lambdas the code might seem a little scary but actually it's pretty declarative and when you get past the logic reads fairly easily.


Monday, 30 March 2015

ChronicleMap - Java Architecture using Off Heap Memory

My last post was written a couple of weeks ago and after some valid feedback I'd like to clarify a couple of points as a preface to this article.

The main takeaway from 'Creating millions of objects with Zero Garbage' should be that with Chronicle you are not 'limited' to using jvm allocated on-heap memory when writing a Java program. Maybe the article would have been more aptly titled 'Creating Millions of Objects using Zero Heap'. Another point I wanted to bring out was that when you have no heap memory you cause no GC activity. 

A source of confusion came from the fact I used the term 'garbage' to describe the objects allocated on the heap. The objects allocated were actually not garbage though they caused GC activity. 

I contrived an example to demonstrate, one, that ChronicleMap does not use heap memory whilst ConcurrentHashMap does, and two, that when you use heap memory you can't ignore the GC. At the very least you need to tune your system carefully to ensure that you don't end up suffering from long GC pauses. This does not mean that there are no issues with allocating from off heap (see the end of this post) and it also does not mean that you can't tune your way through an on heap solution to eliminate GC. Going off heap is by no means a panacea to all Java performance issues but for very specific solutions it can deliver interesting opportunities some of which I will discuss in this post.

Let's examine this problem:

You have multiple JVMs on your machine and need to share data between them. You would also like that data to be persisted so that when the JVMs exits the data does not disappear.

Let's simplify for now and say that you have two JVMs running on the same machine, either or both of which would like to see updates from the other. Each Java program has a ConcurrentHashMap which it updates, those updates are stored and are of course available to it later. But how does the program get the updates applied by the other Java program to its map? And how do we prevent the data from evaporating when the programs exit?

Fundamentally, JDK on-heap collections such as HashMap and ConcurrentHashMap can't be shared directly between JVMs.  This is because heap memory is contained by the JVM through which it was allocated. Additionally when the JVM exits, the memory is released and the data is no longer available, there is no implicit way of persisting the memory outside the lifetime of the JVM. So you need to find some other mechanism to share the data between the JVMs and persist the data.  

From an architectural perspective you need accomplish these two tasks.
  1. A mechanism for sharing the data between the JVMs so that when one process updates the other will be informed of the action.
  2. A mechanism for storing the data so that when one or both JVMs exit the data does not just disappear.
Typically you might use a database as an external sharable store and messaging service to send the data updates to other processes to notify them that some data has been updated.

This results in the following architecture:



The problem with this architecture is that use lose the in-memory speeds of a HashMap, especially if writing to your database is not that fast and you want the write to be persisted before you send the message out over the messaging service. Also many solutions will involve TCP calls which can again be a source of latency. There are of course much faster ways to persist data than writing to a fully fledged database using mechanisms like journaling to disk, for example using a product like ChronicleQueue or similar.  But if you did use a journal you'd still have to build all the logic to recreate a Map data structure on restart not to mention having to keep a Map type structure up-to-date on another JVM.  In addition to the latency introduced by this architecture there is the complication of having to deal with the extra code and configuration for the database and messaging service. 

Even accepting that this sort of functionality can be wrapped up in frameworks, wouldn't it be great if your in memory Map was actually visible outside your JVM.  The Map should be able to implicitly persist the data so that its data is available independently of the life time of the JVM. It should allow access with the same 'memory' speeds as you might achieve using an on heap Map.

This is where ChronicleMap comes in.  ChronicleMap is an implementation of java.util.ConcurrentMap but critically it uses off heap memory which is visible outside the JVM to any other process running on the machine. (For a discussion about on-heap vs off-heap memory see here). 

Each JVM will create a ChronicleMap pointing at the same memory mapped files. When one process writes into its ChronicleMap the other process can instantly (~40 nanoseconds) see the update in its ChronicleMap. Since the data is stored in memory outside the JVM, a JVM exit will not cause any data to be lost.  The data will be held in memory (assuming there was no need for it to be paged out) and when the JVM restarts it can map it back in extremely quickly.  The only way data can be lost is if the OS crashes whilst it has dirty pages that haven't been persisted to disk. The solution to this is use replication which Chronicle supports but is beyond the scope of this post.

The architecture for this is simply this:



For a code example to get started with ChronicleMap see my last post or see the official ChronicleMap tutorial here.

There are a number of caveats and trade-offs to consider before diving into ChronicleMap.
  • The ChronicleMap entries have to be Serializable.  For systems that are very sensitive to performance you will need to implement the custom serialisation provided by Chronicle known as BytesMarshallable. Whilst this is pretty easy to implement it is not something that is necessary with an on-heap map. (Having said that storing data into a database will of course also require some method of serialisation.) 
  • Even with BytesMarshallable serialisation, the overhead of any serialisation might be significant to some systems. In such a scenario it is possible to employ a zero copy technique supported by Chronicle (see my last blog post for more details) to minimise the costs of serialisation. It is however a little trickier to implement than using 'normal' Java.  On the other hand in latency sensitive programs it will have the huge benefit of not creating any objects that might then later need to be cleaned up by the GC.
  • A ChronicleMap does not resize and must therefore be sized up front. This might be an issue if you have no idea how many items to expect.  It should be noted however, that oversizing, at least on Linux, is not a huge problem as Linux passively allocates memory. 
  • Chronicle relies on the OS to asynchronously flush to disk. If you want to be absolutely sure that data has actually been written to disk (as opposed to just being held in memory) you will need to replicate to another machine. In truth any mission critical system should be replicating to another machine so this might not be a big issue in adopting Chronicle.
  • ChronicleMap will be subject to OS memory paging issues. If memory is paged out and has to be swapped back in latency will be introduced into the system. Therefore even though you will be able to create ChronicleMaps with sizes well in excess of main memory, you will have to be aware that paging might occur depending on your access patterns on the data. 



Monday, 16 March 2015

Creating Millions of Objects with Zero Garbage

As noted in First rule of performance optimisation, garbage is the enemy of fast code. Not only can it destroy any sort of deterministic performance by employing the services of the garbage collector but we start filling our CPU caches with garbage that will cause expensive cache misses for our program.

So, can we use Java without creating garbage? Is it possible, for example, in natural Java, to solve this problem:

Create 10m financial instrument objects, store them in a map, retrieve them and perform a calculation using each object without creating any garbage at all.

It is if you use Chronicle!  Chronicle provides libraries so that you can easily use off heap storage in the form off memory mapped files for your objects. (For full source code for this article see here.)

Let's look implementing a solution for the above problem. 

First let's have a look at how you might do this in normal Java so that we make sure we understand the problem and what happens if we use the standard Java libraries for our implementation.

This is the out put from the program:


*** Entering critical section ***
[GC (Allocation Failure)  98816K->92120K(125952K), 0.0317021 secs]
[Full GC (Ergonomics)  92120K->91917K(216576K), 0.2510530 secs]
[GC (Allocation Failure)  125197K->125430K(224256K), 0.0449051 secs]
[GC (Allocation Failure)  166390K->166686K(244224K), 0.0504341 secs]
[Full GC (Ergonomics)  166686K->165777K(387072K), 0.6243385 secs]
[GC (Allocation Failure)  226705K->226513K(388096K), 0.0785121 secs]
[GC (Allocation Failure)  293073K->293497K(392704K), 0.0825828 secs]
[Full GC (Ergonomics)  293497K->292649K(591872K), 1.2479519 secs]
[GC (Allocation Failure)  359209K->359433K(689664K), 0.0666344 secs]
[GC (Allocation Failure)  449033K->449417K(695296K), 0.1759746 secs]
[GC (Allocation Failure)  539017K->539385K(747008K), 0.1907760 secs]
[GC (Allocation Failure)  632569K->633009K(786944K), 0.2293778 secs]
[Full GC (Ergonomics)  633009K->631584K(1085952K), 2.1328028 secs]
[GC (Allocation Failure)  724768K->723368K(1146368K), 0.3092297 secs]
[GC (Allocation Failure)  827816K->825088K(1174016K), 0.3156138 secs]
[GC (Allocation Failure)  929536K->929952K(1207296K), 0.3891754 secs]
[GC (Allocation Failure)  1008800K->1009560K(1273856K), 0.4149915 secs]
[Full GC (Ergonomics)  1009560K->1007636K(1650688K), 3.4521240 secs]
[GC (Allocation Failure)  1086484K->1087425K(1671680K), 0.3884906 secs]
[GC (Allocation Failure)  1195969K->1196129K(1694208K), 0.2905121 secs]
[GC (Allocation Failure)  1304673K->1305257K(1739776K), 0.4291658 secs]
[GC (Allocation Failure)  1432745K->1433137K(1766912K), 0.4470582 secs]
[GC (Allocation Failure)  1560625K->1561697K(1832960K), 0.6003558 secs]
[Full GC (Ergonomics)  1561697K->1558537K(2343936K), 4.9359721 secs]
[GC (Allocation Failure)  1728009K->1730019K(2343936K), 0.7616385 secs]
[GC (Allocation Failure)  1899491K->1901139K(2413056K), 0.5187234 secs]
[Full GC (Ergonomics)  1901139K->1897477K(3119616K), 5.7177263 secs]
[GC (Allocation Failure)  2113029K->2114505K(3119616K), 0.6768888 secs]
[GC (Allocation Failure)  2330057K->2331441K(3171840K), 0.4812436 secs]
[Full GC (Ergonomics)  2331441K->2328578K(3530240K), 6.3054896 secs]
[GC (Allocation Failure)  2600962K->2488834K(3528704K), 0.1580837 secs]
*** Exiting critical section ***
Time for putting 32088
Time for getting 454
[GC (System.gc())  2537859K->2488834K(3547136K), 0.1599314 secs]
[Full GC (System.gc())  2488834K->2488485K(3547136K), 6.2759293 secs]
[GC (System.gc())  2488485K->2488485K(3559936K), 0.0060901 secs]
[Full GC (System.gc())  2488485K->2488485K(3559936K), 6.0975322 secs]
Memory(heap) used 2.6 GB


The two main points that jump out of this issue are, one, the number and expense of the garbage collections (clearly this could be tuned) and two the amount of heap used 2.6GB. In short, there's no getting away from it, this program produces masses of garbage.

Let's try exactly the same thing, this time using ChronicleMap. 

This is the code to solve the problem:

This is the output from the program:


[GC (Allocation Failure)  33280K->6595K(125952K), 0.0072065 secs]
[GC (Allocation Failure)  39875K->12177K(125952K), 0.0106678 secs]
[GC (Allocation Failure)  45457K->15289K(125952K), 0.0068434 secs]
[GC (Allocation Failure)  48569K->18357K(159232K), 0.0098287 secs]
[GC (Allocation Failure)  84917K->21008K(159232K), 0.0156393 secs]
*** Entering critical section ***
*** Exiting critical section ***
Time for putting 8554
Time for getting 4351
[GC (System.gc())  36921K->21516K(230400K), 0.0331916 secs]
[Full GC (System.gc())  21516K->15209K(230400K), 0.0630483 secs]
[GC (System.gc())  15209K->15209K(230912K), 0.0006491 secs]
[Full GC (System.gc())  15209K->15209K(230912K), 0.0234045 secs]
Memory(heap) used 18.2 MB


The main point here is obviously that there were no GCs in the critical section and that the whole program only used 18MB of heap. We have managed to create a program that ordinarily would have produced gigabytes of garbage without producing any garbage at all. 

A note on timings

ChronicleMap is clearly not a drop in replacement for ConcurrentHashMap, they have very different uses and it is beyond the scope of this post to go too much further into that line of discussion. But the main differences in functionality are  that ChronicleMap is persisted and can be shared amongst many JVMs. (ChronicleMap also has the ability to be tcp replicated.) Nevertheless it is interesting to quickly compare timings if nothing else than to make sure we are in the same ball park.  ChronicleMap was faster for putting, 8.5s compared to 32s.  But most of the time in ConcurrentHashMap was spent in GC and that might be tuned away to some extent.  ConcurrentHashMap was faster for getting, 0.5s compared to 4.3s.  Nevertheless on other runs I've seen ConcurrentHashMap taking over 7s because of a GC that occurred in that section. Even though ChronicleMap is doing significantly more work, the lack of garbage produced actually makes the timings comparable with ConcurrentHashMap.

Restarting the program   

Where ChronicleMap really comes into its own is on a restart.  Let's say your program goes down and you need to recalculate the same computation we did earlier.  In the case of ConcurrentHashMap we would have to repopulate the map in exactly the same we did earlier. With ChronicleMap, since the map is persistent it is just a matter of pointing the map at the existing file and rerunning the calculation to produce the totalQuantity.

Summary  


ConcurrentHashMapChronicleMap
gc pausesManyNone
update time32s8s
reads allowing gc7s4s
reads no gc0.5s4s
heap size2.6GB18MB
persistenceNoYes
fast restartNoYes