|
The Java Specialists' Newsletter
Issue 204 2012-08-06
Category:
Book Review
Java version: Java 7 Book Review: The Well-Grounded Java Developerby Dr. Heinz M. KabutzAbstract: Ben Evans and Martijn Verburg explain to us in their new book what it takes to be a well-grounded Java developer. The book contains a section on the new Java 7 features and also vital techniques that we use for producing robust and performant systems.

Welcome to the 204th issue of The Java(tm) Specialists' Newsletter sent to you from a warm
Crete. We spoke to a French art dealer in the sea at
Kalathas today and asked him why he came to Crete on holiday.
We have a lot of Italians, French and Russians here this
year, plus of course thousands of Scandinavians. He told us
that countries like Croatia had become overrun with visitors
and also quite expensive for holidays. In comparison to
other popular destinations, Crete offered excellent value
for money. It is true. The prices in restaurants have
not changed much since 2006. I can get a delicious freshly
squeezed orange juice in Chorafakia for just 2 Euro at
Pantelis' cafeteria. And nothing beats the cooking of
Irene's next door. The only group of holiday makers that
is missing this year are my fellow Germans. Angsthasen ;-)
A few weeks ago, we went to apply for a Greek ID card for my
14 year old son. When we came to Greece, my name was
converted into the Greek alphabet as XAINZ KAMPOYTZ. Greek
does not have an "H" sound, so they used "X", which is
pronounced as "CH". The "U" sound is made up of the
diphthong "OY". Unfortunately someone had
the bright idea of automatically reverse engineering Latin
from Greek names. So the computer was fired up somewhere in
Athens and converted me to CHAINZ KAMPOUTZ. Knowing how much
trouble incorrect names can cause, I asked them to fix it.
This turned out to be rather difficult for them. After all,
who can argue with a computer translation? At one point, the
policewoman tried to convince me that their system was
correct and that I had it wrong. Gosh, 40 years of being
called Heinz (like ketchup) Kabutz and only now I find out
that it was wrong all the time? Must let my mom know!
Thanks for reading this newsletter on our website. We also have a mailing list. That is where the real action takes place (webinars, free reports, etc.). Maybe subscribe today?
Advanced Java Courses on Crete:Java Specialists Master Course 18-21 June 2013 and
Concurrency Specialists Course 6-9 August 2013.
Ben Evans and Martjin Verburg are both well known Java
experts who consult in the financial industry in London.
They have many years of experience as well-grounded Java
developers. Together they wrote The Well-Grounded Java Developer: Vital
techniques of Java 7 and polyglot programming.
They kindly asked me to write the foreword, which you can read
on
Manning's website. Hope you enjoy it. It is a bit
different. And yes, the fact that I wrote the foreword is an
endorsement for the book. I certainly would not have agreed
if I did not like the book. I had the great privilege of
getting a sneak peak at the book and also to meet Ben and
Marty in person when they came to Crete in April.
Cost of Change in Java
In the book they explain why it is so expensive to add new
features to the JVM. Adding new library extensions such as
fork/join or syntactic sugar like switch-on-string
is relatively easy, but a JVM instruction such as
invokedynamic is very costly to add. This is why we have not
seen many changes to Java's fundamental infrastructure since
Java 1.0. I was always wondering why change flowed so slowly
in the Java environment. This is all described in chapter
1 of the book, which you can download
as a sample chapter.
Binary Literals
One of the new features in Java 7 are binary literals.
We can now write numbers as 0b101010101. Unfortunately you
are also allowed to write long binary numbers with a lower
case L, such as: 0b111110111011101101101l. This is quite
confusing to readers of the code, as they can easily mistake
the lower case L at the end of the number for a one. It is
much clearer to write 0b111110111011101101101L. I would have
welcomed it if they had decided to not allow the lower case L
for binary numbers, but they probably wanted to stay
consistent with other primitive number representations.
Better Exceptions
In previous versions of Java, if we caught the general
"Exception" and then re-threw that, we needed to declare
that our method throws "Exception" as well:
public void foo() throws Exception {
try {
doSomethingWhichMightThrowIOException();
doSomethingElseWhichMightThrowSQLException();
} catch (Exception e) {
// do something with e ...
throw e;
}
}
In Java 7, the compiler is clever enough to figure out that
only the checked exceptions need to be declared. Thus we can
write:
public void foo() throws IOException, SQLException {
try {
doSomethingWhichMightThrowIOException();
doSomethingElseWhichMightThrowSQLException();
} catch (Exception e) {
// do something with e ...
throw e;
}
}
In the book, Ben and Martijn recommend that you mark the
Exception as "final". The compiler does not insist on this,
so it is just a convention that they use to signal their
intention. In my opinion, this is not necessary, since a lot
of code already would have the exception marked as final.
Try-With-Resource
They make an important point about try-with-resource. We
need to declare each of the objects that we want to have
automatically closed in the try section. For example, this
would not be correct:
try (
ObjectInputStream in = new ObjectInputStream(
new BufferedInputStream(
new FileInputStream("someFile.bin")));
) {
// use the ObjectInputStream
}
If the FileInputStream construction succeeds (because the
file does exist) but the ObjectInputStream construction fails
(because the file is corrupt) or the BufferedInputStream
fails (because of an OutOfMemoryError), then the
FileInputStream will not be closed automatically.
The correct way to write the code is like this:
try (
FileInputStream fis = new FileInputStream("someFile.bin");
BufferedInputStream bis = new BufferedInputStream(fis);
ObjectInputStream in = new ObjectInputStream(bis);
) {
// use the ObjectInputStream
}
Now, if any part of the construction fails, the previously
declared and constructed objects will be automatically
closed.
The "javap" Tool
The book contains a nice discussion on the javap tool and how
we can use it to analyse what is going on in our code. I
have mentioned the javap tool in 15 newsletters already
(042,
064,
066,
068,
069,
083,
091,
105,
109,
115,
129,
136,
137,
147 and
174). As you can
imagine, it is a technique that I often employ to understand
what the byte code looks like. However, I do not recall
seeing it written about in any Java book to date,
at least at the level that Ben and Martijn did. Jack Shirazi
mentioned javap very briefly in his excellent book,
Java Performance Tuning.
Warning: Even though Shirazi's book is fantastic, it is quite
dated. As always with clever performance tricks, you need
to measure that the tricks works for you. Some parts of his
book, such as his methodologies for tuning performance, are
still very relevant today.
Bottleneck on Caches
One of the most surprising classes was their CacheTester.
I have seen a number of benchmarks that try to show how fast
Fork/Join is by iterating over a very large array in
parallel. For example, the code might try to find the
largest int inside the array.
Usually the benchmark bottlenecks on memory, thus incorrectly
proving that fork/join does not give any performance gains.
In the CacheTester, Ben and Marty show how iterating over
the array one element at the time is not much slower than
looking at every 16th element. Here is their CacheTester:
public class CacheTester {
private final int ARR_SIZE = 1 * 1024 * 1024;
private final int[] arr = new int[ARR_SIZE];
private void doLoop2() {
for (int i=0; i<arr.length; i++) arr[i]++;
}
private void doLoop1() {
for (int i=0; i<arr.length; i += 16) arr[i]++;
}
private void run() {
for (int i=0; i<10000; i++) {
doLoop1();
doLoop2();
}
for (int i=0; i<100; i++) {
long t0 = System.nanoTime();
doLoop1();
long t1 = System.nanoTime();
doLoop2();
long t2 = System.nanoTime();
long el = t1 - t0;
long el2 = t2 - t1;
System.out.println("Loop1: "+ el +" nanos ; Loop2: "+ el2);
}
}
public static void main(String[] args) {
CacheTester ct = new CacheTester();
ct.run();
}
}
I ran their code on my 8-core server and got the following
results in microseconds:
Average Variance
Loop1 239 12
Loop2 549 48
We can thus see that even though we are reading 16x as many
array elements, it is only 2.3 times slower to do that.
Even though the results are good in that the variance is not
too high, they could be better if we changed a couple of
things. First off, to measure time at the nanosecond
granularity invites slight abberations in the system to have
an influence on our variance. In my CacheTester, I repeat
the iteration 1000 times, thus getting the results in
milliseconds. Secondly, I usually try to produce output
that I can then copy and paste directly into a spreadsheet.
Comma separated values seem to work nicely.
Thirdly, the number 10000 in the CacheTester
is significant. Typically, after you have called a method
10000 times, the HotSpot compiler starts profiling
and optimizing the code. However, it may be a while before
the new optimized code is available. Thus we sleep for a
second after the 10000 warm-up calls in order to immediately
have the fastest times:
public class CacheTester {
private final int ARR_SIZE = 1 * 1024 * 1024;
private final int[] arr = new int[ARR_SIZE];
private static final int REPEATS = 1000;
private void doLoop2() {
for (int i = 0; i < arr.length; i++) arr[i]++;
}
private void doLoop1() {
for (int i = 0; i < arr.length; i += 16) arr[i]++;
}
private void run() throws InterruptedException {
for (int i = 0; i < 10000; i++) {
doLoop1();
doLoop2();
}
Thread.sleep(1000); // allow the hotspot compiler to work
System.out.println("Loop1,Loop2");
for (int i = 0; i < 100; i++) {
long t0 = System.currentTimeMillis();
for (int j = 0; j < REPEATS; j++) doLoop1();
long t1 = System.currentTimeMillis();
for (int j = 0; j < REPEATS; j++) doLoop2();
long t2 = System.currentTimeMillis();
long el = t1 - t0;
long el2 = t2 - t1;
System.out.println(el + "," + el2);
}
}
public static void main(String[] args)
throws InterruptedException {
CacheTester ct = new CacheTester();
ct.run();
}
}
Here are the results of my CacheTester, which show almost no variance at all:
Average Variance
Loop1 238 0.3
Loop2 546 1.8
When I ran the code on my MacBook Pro with an Intel Core 2
Duo, I had the following results with my benchmark:
Average Variance
Loop1 168 17
Loop2 580 37
You can see that the variance was again quite high,
because my laptop had too many other programs running on
it. On my MacBook Pro hardware, iterating through every
element in the array was 3.4 times slower.
Concurrency
Another sample chapter you
can download is the one on concurrency. Both Ben and
Martijn are certified to present my concurrency
course. Ben has a lot of experience in the
subject, which led to many interesting discussions when
they came here in April.
Just one minor gripe. In the book
they use Math.random() * 10 in order to
calculate a random delay. Since Java 7, we should rather use
ThreadLocalRandom.current().nextInt(10). This
has several benefits. First off, ThreadLocalRandom keeps a
Random instance per thread, so that we do not have any
contention on the random seed. Secondly, the random
distribution is fairer with the nextInt(10)
method call. The fairness is a minor point, but the
contention is not. Math.random() shares an instance of
Random and the seed is protected by compare and swap. Thus
if a lot of threads call this at the same time, they will
need to repeat a lot of expensive calculations to eventually
update the random seed.
The book is filled with many other interesting tidbits,
and is definitely on my "recommended reading" list for
the Java specialist.
Kind regards
Heinz
P.S. Hot off the press: Martin Thompson just published an
article on the cost of memory access, showing different
approaches to traversing the elements. This is closely
related to the CacheTester in Evans and Verburg. Click
here to read it.
Book Review Articles
Related Java Course
Discuss at The Java Specialist Club
|