Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Thursday, August 18, 2011

Big Map, String, GC, Memory Consumption and Performance

Hashtables are used in many cases to cache information in memory. There are other alternatives, like in memory databases, or flushing data to disk, but when all you need is a simple key-value relation and you need very high performance, the simple Java HashMap can do the trick. Usually cache can allow loosing information that was not claimed recently, assuming that recent usage of the data can foresee higher chances of this data to be claimed again soon, compared to less recently used data. But in some cases we prefer the cache to hold as much information as possible, which means a VERY BIG MAP.

Important morals below are based on an application with HashMap holding 20M entries, in 64bit JVM and 6 GB (-d64 -Xmx6g ). The Map itself uses much less memory, but in order for the entire application to work smoothly we need the 6 GB.

When coming to very big HashMap, there are a few considerations to take into account:


  1. The default load factor of 0.75 is reasonable. Don't play with it without understanding what you are doing. The load factor determines when the map should grow and rehash should occur, this happens when number of elements in the map gets above the result of (capacity * load factor). Higher values of load factor means less rehash operations, but a map that is more dense. Calls to get in a dense map are more costly since buckets are full with many entries that shall be iterated. Lower load factor values will create more rehash operations and a sparse map, which is more memory consuming but more efficient in get calls. It should be noticed that load factor can be bigger than 1, which means we allow the table (number of buckets) to be smaller than number of entries.

    To summarize: don't start with optimizing the load factor, it's a bad start.

  2. Initial capacity is important. Starting the HashMap too small will result with rehash operations upon calls to put. Though HashMap doubles itself on resize, still failing to provide appropriate initial capacity may result with a few costly resize cycles.
    The recommended initial capacity, for load factor of 0.75, is: 1.5 * numElements
    (Read more: http://stackoverflow.com/questions/434989/hashmap-intialization-parameters-load-initialcapacity).

    In our case, the performance difference of setting 20M entries into the map, with setting the initial capacity to 30M (Java will in fact set the initial capacity to the closest power of 2 above this size), compared to no initial capacity set, was almost double the insertion time, with very clear workload on rehash and GC work following it.

    To summarize: initial capacity is important. Set it!

  3. Beware of memory leaks!
    Maps, as any long-leaving memory storage entity, are a potential source for leaks.
    In our case, the Map got Strings from file, but needed to use for the entries' keys only a small portion of each line read from the file. Thus, after reading each line from the file, some string tokenizing was done resulting with the key and value to store.
    Unfortunately the entire line read from file is kept in memory, even though we need only part of it... This is due to the way substring and other similar tokenizing methods are implemented, which return a new String, that holds the old one with offsets inside. This is a known issue (for example see: http://eyalsch.wordpress.com/2009/10/27/stringleaks/).
    Solution is to manually create a new String and give it the substring portion you want to keep:
    String key = new String(bigLine.substring(from, to));
    In our case, fixing a similar issue resulted with requiring 1.5GB less memory for the exact same scenario! It turned out that the map keys' kept a long tail that should have been cut.

    To summarize: beware of holding a substring in a Map! Do it the right way.

  4. In such a huge map, every byte in the key-value becomes 20M in the entire map. Thus saving a few bytes can worth the hassle. For example, instead of using String as the value, we decided to use char array (a simple char[]) thus saving: [1] a reference to the string object from the hashmap entry - instead holding a direct reference to char[]. In 64 bit this values to 8 bytes saving! [2] three int fields that are in String class: offset, count and hashmap. The offset and count fields are there to allow String to point at a position which is partial in the char[] it holds (directly related to the substring leak mention in item 3 above). We don't need this info. Also caching the hashmap value is redundant, as the Map entry itself does it.

    By using char[] instead of String as our value, we save 20 bytes per entry, which is 400MB total! And without hurting the code or making it more complex.

    Our key is also a String, but it cannot be turned into char[], as we need a proper hashcode and equals functions. Still, one should consider implementing a dedicated Key class that holds only the relevant info, char[] in our case. Since the extra reference cannot be saved, the saving here could be the 3 int fields in class String, which totals to 240MB for 20M entries.

    To summarize: caching Strings in big Maps is costly. Think how to reduce the size of your key and value!

  5. The hashcode is crucial. Bad hashcode on the key, which creates bad distribution (too many keys get the same hashcode value) can be performance devastating. On the other hand, though of much less importance, a more efficient hashcode method has an influence over at least 20M insertion operations. Read how String hashcode is implemented to get the idea: http://stackoverflow.com/questions/299304/why-does-javas-hashcode-in-string-use-31-as-a-multiplier

    To summarize: check your key hashcode method, use some real data and validate that you get reasonable distribution. If needed, work on improving the hashcode method by using higher power multiplication on the key fields.

  6. Know when to stop optimizing. In some cases you see a programmer keep optimizing when there is really nothing more to optimize (well, there is always more to optimize, but then it may result with taking parts to C and using JNI, or use direct byte arrays, things you would better not go to, if not really needed...).
    Calculate the most minimal amount of memory you need, save your time and don't try to optimize your application into less.

    To summarize: set goals to your optimization efforts. Make sure the goals are not too aggressive, i.e. are feasible without re-writing the entire thing.

  7. The entries of the big map will eventually arrive to the GC old-generation section. It means that in the described usage the old generation is going to be very big. There are two options to handle this, one is to play with the generation ratios, the other option is to set a big enough total heap memory to allow the old generation to be big enough. If you have enough resources on the machine you can go with the second option, otherwise you would need to dig into the generation ratio configuration.

    To summarize: size of the old generation section is crucial to prevent unnecessary full GC cycles. Configure the max heap size and/or the generation ratios.

By optimizing the memory consumtion we got also much better performance, as the application went into less GC cycles. Some other parts of the drill include removing unused entries from the cache, in a dedicated thread, when reaching certain threshold, and more... The result is a huge cache running smoothly on a reasonable sized server.


Thursday, August 19, 2010

Semi-colon and java.lang.OutOfMemoryError

; ; ; ; ; ; ; ; ; ; ; ; ; ;

I want to share with you a crash at customer site caused by java.lang.OutOfMemoryError.

Here is the original code:

if (synchRemove(lobj.getSeqNum()) != null);
____timeoutedList.add(lobj);

Can you see the problem?

(Well it's much easier after the relevant lines of code are isolated. In reality it took a few days and nights to get to these lines, remember it occurred in customer environment where not all relevant info is easily available for the development team. The OOM doesn't necessarily occur at this line).


Moral:

  1. Small semicolon can cause big troubles
  2. It's hard to see everything in code review. A trouble-making redundant semicolon can skip the eyes of the reviewer
  3. Load test may find such cases (but may still miss them, if the relevant scenario was not created)
  4. Good unit tests may also help
  5. Most Coding Guidelines require curly brackets for any block, even containing only one line. This could possibly reveal the error (if not by the developer, by the reviewer in code review)
  6. Static code analysis tools, like FindBugs – which is free, do point at such errors!
  7. In some IDEs (e.g. Eclipse) you can configure the IDE to present warnings on such cases

in the above case, analyzing the heap dump, using MAT, led to the problematic giant list, then tracking all insertions into the list in the code, led to the faulty line.


Monday, May 31, 2010

Java Unit Testing

There are a lot of tools and options out there for java unit testing, replacing JUnit or on-top of JUnit. Let's do some order in things.

1. TestNG vs. JUnit4

TestNG came out to add features that were missing in JUnit 3.x.
It did quite a good job and some may say that TestNG 5.10+ is still better than JUnit 4.7+
TestNG has better data providing mechanism.
TestNG has test dependency definition, missing in JUnit.
TestNG has group level with fixtures on group. And it also has the ability to create automatic run file for all failed tests.
All that said, JUnit has more extending libraries using its ability to extend JUnit TestCase and JUnit TestRunner.
Both have very good IDE support, as well as Ant and Maven support.

Coming to choose, both are good. I stick with JUnit.

Note that JUnit 4 made the mistake of integrating another library into its jar, that is the hamcrest core. They should have known better than that... The problem is that when you get the full hamcrest lib (to get the really powerful matchers that you need in your tests) it gets confused with the lib that came with JUnit. Solution is simple: put the full hamcrest that you bring to be first in the classpath, before the JUnit jar.


2. Load

JUnitPerf do the job with its ability to run tests in several threads.
It's easy and useful, for integration stage and for any piece of code that may act differently under load.

The following code was tested with JUnitPerf and it catches the bug of not synchronizing the addIfAbsent:

public class MyList extends ArrayList
// we test with the synchronized and without
public synchronized boolean addIfAbsent(Object o)
boolean absent = !super.contains(o);if(absent) {
super.add(o);
}
return absent;
}
}


The test that catches the bug when the synchronized is deleted:

public class MyListTest extends TestCase {
private MyList myList = new MyList();
private int count = 0;
MyListTest(String name) {
super(name);
}

TestSuite suite = new TestSuite();
Test testToRun = new MyListTest("testMyList");
int numUsers = 1000;
int iterations = 5;
suite.addTest(new LoadTest(testToRun, numUsers, iterations));
return suite;
}
public void testMyList() {
if(myList.addIfAbsent(count)) {
// there is a gap here that may cause error on correct
// implementation, in case of thread switch at this point
synchronized (this) {
++count;
assertEquals(myList.size(), count);
}
}
}
}


This solution is not bullet proof.
Theoretically it may give errors on correct scenario, and of course it can always miss bad implementation, as it’s a matter of timing.
However, in reality it does catch the problem!
JUnitPerf is simple and gets the job done.


3. Thread testing

It could be nice if we could have time the behavior of the code under test, to mimic race conditions and check how the code operates. The idea is to deliberately create race condition then to check that a synchronization lock is working as expected, ensure that unlocked blocks are fine, check for deadlocks and see if there data corruption.

With such ability we could have create the race condition in the above code with a deterministic approach instead of using load.

Suggested tools:

  • thread-weaver (version 0.1)
    allows the test to time different threads reach code points in a certain order, either explicitly or “semi-automatically”
  • MultiThreadedTC (version 1.01)
    allows to set time tickers on objects in the test itself, but NOT on the tested code, thus less relevant for most testing purposes

Both tools are not highly supported, recommending still not to rush into it…


4. Mock Objects

There are 3 cases where you need a Mock Object in your test:

(a) Tested code gets a complicated object as parameter
Solution: Mock the parameter with a Stub/Mock implementation

(b) Tested code invokes a static call on some resource
Solution: Replace the static call with a Stub/Mock implementation

(c) Tested code creates complicated objects
Solution: Replace the created objects with a Stub/Mock implementation

The Mock utilities rely on one of the following technologies: java.reflection.Proxy, replacing the ClassLoader, byte code instrumentation and AOP weaving (which relies by itself on replacing the ClassLoader or on byte code instrumentation).

Possible tools: Mockito, JMock, JMockit, EasyMock, JEasyTest and many others…

There are differences between the tools in syntax and abilities.
Tools that rely on Proxy cannot mock static behavior and object creation.

The two possibile combinations that came to my final round were:
(a) JMockit 0.998
(b) EasyMock 3.0 + PowerMock .1.3.8

JMockit seems powerful and well documented, but in one of my tests the test failed without a reason and when I debugged it I saw that some exception is thrown within the JMockit code itself. Probably I did something wrong in the test, but still this is not what I expect for. Maybe the 1.0 version would be better...

My selection here would be EasyMock + PowerMock


5. Other tools

Other tools in this domain worth mentioning:
- DBUnit
- HttpUnit (not only for tests)
- Selenium (it is a good tool for Web UI tests in general, and you can operate it from your java unit tests if you'd like)

Sunday, November 22, 2009

Array of Generic Type

I was asked why one cannot create an array of generic type, which means this line cannot compile:


// the below doesn't compile
ArrayList<Integer>[] arrayOfLists = new ArrayList<Integer>[7];


The compiler doesn't like the creation part, on the right, while the declaration alone, on the left, goes fine. This issue is widly discussed, e.g. nicely described here.

There are many ideas why it is forbidden and many suggestions for workarounds. But the nice colleague who asked me this question liked the most the workaround I suggested him, which is very simple.

I started by asking why he needs this.

And he explained that he needs to manage some info for 7 days a week:


// the below doesn't compile
ArrayList<SomeInfo>[] weeklyInfo = new ArrayList<SomeInfo>[7];


I suggested to create two new classes, DailyInfo and WeeklyInfo:


class DailyInfo {
    private ArrayList<SomeInfo> dailyInfo = new ArrayList<SomeInfo>();
    ...
}

class WeeklyInfo {
    private DailyInfo[] dailyInfo = new DailyInfo[7];
    ...
}


In most cases this would be the right object oriented approach, which by the way solves the problem of array of generic type.

Wednesday, September 23, 2009

Auto-unboxing hazard -- the three states of Boolean: TRUE, FALSE, and...

What do you say about the following code:

boolean isActive = (Boolean) pageContext.getAttribute(IS_ACTIVE);
if(isActive) {
// dosomething
}

If you are worried about the casting to Boolean in case where attribute IS_ACTIVE doesn't exist, don't worry, it's OK to cast null to any type, null stays null.

So, is it OK?

Well, no. The problem is with the auto-unboxing to primitive type boolean, which throws NullPointerException if the Boolean is null. Which is a bit hidden from the programmer who might miss this hazard when writing the code.

How should we fix it?

Is the following a decent fix:

Boolean isActive = (Boolean) pageContext.getAttribute(IS_ACTIVE);
if(isActive) {
// dosomething
}

If you ask "what's the fix?" - we turned the variable isActive to be Boolean rather than boolean. Which should eliminate any NullPointerException on the first line! But only in order to pass the exact same NullPointerException into the 'if'...

So, what about:

Boolean isActive = (Boolean) pageContext.getAttribute(IS_ACTIVE);
if(isActive.booleanValue()) {
// dosomething
}

Yes, you are right, same NullPointerException. Maybe now even more explicit.

We could go with:

Boolean isActive = (Boolean) pageContext.getAttribute(IS_ACTIVE);
if(isActive != null && isActive) {
// dosomething
}

But I believe this looks better:

Boolean isActive = (Boolean) pageContext.getAttribute(IS_ACTIVE);
if(Boolean.TRUE.equals(isActive)) {
// dosomething
}

Tuesday, June 23, 2009

JavaDay, JavaOne - Java Thoughts

I was attending Sun's Java Day this week, here in Israel. The main themes Sun is pushing are JavaFX and MySQL.

Some thoughts on both, as well as other topics.

JavaFX

Sun's interest here is clear. Every handset or any sort of device running JavaFX means more licensing fees for Sun. In fact, JavaME is one of the only Java environments which Sun's really see money from, at least for now. This would probably change in the future with much more agressive sell of JavaSE licenses for extra features and support, but still handsets is a very important addressable market, competing with others technologies such as the simple browser, Android, iPhone Objective-C, etc. Sun interest is to have as many applications as possible running on top of JavaME, JavaFX and LWUIT (the last two are a bit competing on the same square). More applications means more consumer power (consumers who want to run these applications) thus more pressure on the handsets manufectures to have JavaME and JavaFX on the handset, droping the required license fee at Sun's pocket.

All fine till this point. But now it comes to JavaDay event, or JavaOne conference in SF. These events shall reflect what interests the Java community, be judgmental, compare competing technologies. But instead it becomes more and more a marketing event for what Sun is trying to push. Thus, JavaFX catch very high percents of all sessions (even a session on Maven had to be called "Deploying JavaFX apps with Maven" or something alike to get in...) And it is totally lacking the critic perspective, comparing with other competing technologies such as Android, GWT, zool, YUI, Adobe AIR etc.

Is JavaFX interesting? probably yes, but not at the amounts poured at us.

As for the judgmental perspective, I'm missing in JavaFX the separation between content and style, without that this would be a step back. Does JavaFX work with CSS? not that I know of. Is there a similar alternative? The declarative approach of JavaFX is good, but why the choice of JSON-like declarations and not XHTML? The language is very close to Groovy, but not exactly the same (Groovy is from Spring creators), is there a path to consolidate these two new scripting languages? And why should we go with a propraietary environment and language, and not with W3C standards adopted by the browsers running on mostly all handsets? ... Yes, I know there are answers for some of the questions above, but in the huge time dedicated for JavaFX, alternatives, as well as known flaws and drawbacks, were not discussed.

Which brings me to the thought that independent Java events, such as
Devoxx, are a better meeting place for the community.


MySQL

This was another theme at the event. MySQL is a powerful tool, the regulator shall take it out of the hands of Oracle to preserve competition.


Java Reference App

Together with Ronen Perez, I was presenting a session called "Java Reference Application - putting all the buzzwords together" focusing on our selected libraries and tools.


Java Community and New Technologies

It's good to see all these smart people together. Being smart, people follow the trends and new technologies carefully, asking "what is it for me? how does it make my life easier?" In the past you could see people enthusiastic about any new thing, today you see people much more careful and reluctant. It's either that we are fed up with disappointments from previous "innovations" or that the pace of things require us all to be more cautious and conservative. This, together with the competition between technologies, requires new technologies to be well cooked and invest much more effort in getting us in.

Friday, August 29, 2008

Adding abilites to Java's ThreadPoolExecutor

I had a discussion on Java's ThreadPoolExecuter, which seem not to have the ability to block producer from adding tasks when the thread pool's queue is full. I went through some possible solutions and I'm gonna write something about it, so please stay tuned (and if you have something to tip on this issue, say it now).

-----------------
Happy to update:
I've published it, in java.net, it is called Creating a NotifyingBlockingThreadPoolExecuter.

Tuesday, June 24, 2008

Eclipse Plugins Tutorial (OOPSLA '07)



I was asked where can people get the materials from the tutorial on "Creating Plug-ins and Applications on Eclipse Platform" that Alex Romanov and I gave in Montreal on OOPSLA '07. It was while ago, and the materials are on the web already. But here are the links, as it seems google is not doing a good job referring people to the googlegroups site we created...
The tutorial deals with all the important things for getting started with Eclipse plug-ins: creating a plug-in project, the components of a plug-in project, GEF, MVC in the Eclipse Plug-in architecture, SWT, Actions, RCP and more.

So here is the companion booklet.
And here are the slides.
Both can be printed or redistributed, as is, for any purpose, referring back to the source.

Hope you can find it useful.


Monday, May 26, 2008

Java Generics, Erasure, Reification

I gave today a talk in Sun's Java Day 2008, Israel, on Java Generics, Erasure, Subtyping, Super Type, Wildcards and a few words on Reification.

Though it's a bit of an old, Java 5, topic, and though the use of Generics is straight forward, the deeper understanding of why one gets type unsafe warnings, how to generify your own utility classes etc. - this needs some more attention. So I still find it relevant to talk about and it seems that it was relevant for most of the audience (I hope! ... well, in fact this is the feedback I got after the talk).

The presentation itself is available in pdf format here.

I want just to post here a small piece of code from the presentation. The main point of this code is how you can cast an object to T when T was actually erased and you don't have it in run-time.
The important lines are emphasized.


public <T> T getContent(Column<T> column) {
Object colContent;
String colName = column.getName();
if(colName != null) {
colContent = getContent(colName);
}
else {
Integer index = column.getIndex();
// TODO: if null throw something of your own
// (for now it will be NullPointerException)
colContent = getContent(index);
}
if(colContent == null) {
return null;
}

// now we need to cast the returned type and that's not trivial

// need raw type as a bypass
// usually use Class<?> when the type of the Class is unknown
// but it won't work here!
@SuppressWarnings("unchecked")
Class colClass = colContent.getClass();

// we know that we hold the correct type
@SuppressWarnings("unchecked")
Class<T> colClassT = colClass;

// now we can use the cast method of Class<T>
// If we were wrong regarding the type ClassCastException
// will be thrown (we can wrap with catch and replace with
// some application exception if necessary)
return colClassT.cast(colContent);
}

--------------------------------------
Addition, 27/05/2008:
--------------------------------------
A day after posting this, I got a very innocent question on the code: why not just use simple casting to T: return (T)colContent;
Well, I remembered that it's Not OK. But checking it again I recalled that Not OK means here that you get 'Type Safety: Unchecked Cast' warning, yet it does compile. What the compiler tells you is that it cannot check that the casting is fine at compile time, which is OK with us.
(Isn't it always the case that the compiler cannot check down casting at compile time? raises the question why Java decided to give a warning on this...).
So, a simpler version of the code would be of course:

// ...
// now we need to cast the returned type
// we prefer to use a local var so we can put
// the SuppressWarning here and not on the method
// (you cannot put it on a return statement)
@SuppressWarnings("unchecked")
T retval = (T)colContent;
return retval;
}

Now, one can just ask: so what's all this thing with the Class<T>.cast() method if we can directly use the "plain old" casting way of (T).
Well, in cases where Class<T>.cast() saves the 'type safety' warning, it worth using it. In our example above we get the warning anyhow (for the implementation using Class<T>.cast() we even get two), so in such a case it would be a better choice to go back to the "plain old" casting way of (T).
For an example where Class<T>.cast() indeed omits the 'type safety' warning, see item 29 in "Effective Java", details below.
Of course, if you do need the Class<T> instance itself, e.g. for reflection purposes, the original way presented above would be relevant.

------------------------------------------------------
End of Addition, 27/05/2008.
------------------------------------------------------

The idea behind the code is based on Josh Bloch's "Effective Java" 2nd Edition, item 29.


(The code example is not in the book, it's my example based on item 29. So in case of any error please find me responsible...)