Friday, July 15, 2005

Application Versioning

Tom ball explains how class loading can be used to achieve simple application versioning -

Versioning, which I'm defining for this entry as how a Java application manages its external library dependencies, has been a tough issue ever since Java first released. Back when Java was born, the vision was that each machine would have a single Java runtime and standard libraries which would always be fully backwards-compatible. The reality has been that for most apps, the only reasonable alternative to testing a full matrix of released JREs and libraries is to instead package everything the app needs, install the whole hairball on each customer's system and use a custom classpath to access it. The problem with a custom classpath is that it is easy for your customers to break in subtle (and not so subtle) ways, which makes them cranky and can drive your tech support engineers crazy. Some work has been done in the JDK via its Package Versioning Specification and API, but there are still times when your app really needs to keep specific libraries under tight control.

NetBeans has this problem with its javac bridge, which allows its editor and refactoring modules access to javac's error checking and parsing support. The problem is that javac doesn't have a public API, so while a tool can leverage a specific version of javac, it cannot rely on whatever is on the customer's machine since its internal API may be radically different. We have a recent version of javac that works with our bridge, but just adding it to the NetBeans classpath won't work for two reasons:
  • NetBeans supports many different JDKs, each of which have their own version of javac; and
  • The Mac OS X includes the javac classes in its bootclasspath (and supportable products shouldn't whack the bootclasspath if possible).
The solution proved fairly trivial to implement while being quite robust: define a ClassLoader to isolate the bridge's use of javac classes from the rest of the IDE. Here is the ClassLoader implementation we use; NetBeans has several similar loaders for specific modules, but Tomas Hurka wrote this Factory class for the javac bridge:

    private static class GJASTClassLoader extends URLClassLoader {
private final PermissionCollection permissions = new Permissions();

public GJASTClassLoader(URL gjastJar) {
super(new URL[] {gjastJar}, Factory.class.getClassLoader());
permissions.add(new AllPermission());
}

protected Class loadClass(String n, boolean r) throws ClassNotFoundException {
if (n.startsWith("com.sun.tools.javac") || n.startsWith("org.netbeans.lib.gjast")) {
// Do not proxy to parent!
Class c = findLoadedClass(n);
if (c != null) return c;
c = findClass(n);
if (r) resolveClass(c);
return c;
} else {
return super.loadClass(n, r);
}
}

protected PermissionCollection getPermissions(CodeSource codesource) {
return permissions;
}
}
As you can see, we rely on URLClassLoader to do all the heavy lifting. Our version isolation support is in loadClass(), where a test is made of the requested class name to see if it is in one of the packages to be isolated (here, we test whether the class is a javac or bridge class). If it is an isolated class, URLClassLoader.findLoadedClass() and findClass() look it up in the jar file we supplied in the constructor; otherwise we let URLClassLoader.loadClass() delegate to the parent classloader.

Now, we need to interact with classes loaded by this classloader. What works best for us is to define a simple interface which the versioned classes and their client code shares, and a factory class that uses reflection to load the class which implements that interface (in 1.0, you needed a default constructor and used Class.newInstance()). Here's a simplified example (from the same Factory class):

public interface ErrorChecker {
int parse() throws CompilerException;
}

public final class Factory {
private static Factory instance = null;
private static Constructor newErrorChecker;

public static synchronized Factory getDefault() {
if (instance == null) {
instance = new Factory();
Class[] newCheckerTypes = new Class[] {
ECRequestDesc.class
};

File gjastJar = InstalledFileLocator.getDefault().locate("modules/ext/gjast.jar", "org.netbeans.modules.javacore", false);
try {
ClassLoader loader = new GJASTClassLoader(gjastJar.toURI().toURL());
Class c = Class.forName("org.netbeans.lib.gjast.ASErrorChecker", true, loader);
newErrorChecker = c.getConstructor(newCheckerTypes);
} catch (Exception e) {
}
}
return instance;
}

public ErrorChecker getErrorChecker(ECRequestDesc desc) {
try {
return (ErrorChecker) newErrorChecker.newInstance(new Object[] { desc });
} catch (Exception e) {
Throwable t = e.getCause();
throw new RuntimeException("Cannot create errorChecker: " +
t != null ? t : e);
}
}
}
In the above, we fetch the ASTErrorChecker constructor via reflection, then use it whenever the client requests a new ErrorChecker implementation. Because the interface doesn't directly or indirectly reference any class types in our private javac copy (CompilerException is also shared), objects created using its classes can interact with the client without conflict.

There is one thing to watch for (there always is), however: sometimes you can find yourself pondering the impossible, like I did yesterday:

debugshot.png

What caught me off-guard is that the debugger shows the type of "ex" is EmptyScriptException, but if it were that type then it should have been caught by previous catch block. Worse, a "(ex instanceof EmptyScriptException)" watchpoint returns "false", when it "obviously" should be true. The issue is that a class isn't just defined by its bytecode (the classfile's contents), but by the combination of bytecode and classloader. Here, there were two copies of EmptyScriptException loaded: once by Jackpot's private classloader, and once by the NetBeans one. Instances of one class copy will fail instanceof and catch tests with the other. I frequently forget this subtlety until reminded by a few head bangs against my monitor. The fix is to add the class to your list of classes which your classloader ignores and therefore shares with its parent classloader.

Over time, I have learned the value of this behavior (the classes not mixing, not the head banging). Since I'm pretty lazy, the extra work required to share classes between classloaders means that my designs do as little class sharing as possible. It is easier to maintain a really strict isolation with only a few, simple interfaces, than it is to maintain a big list of shared classes and deal with the headaches of managing their dependencies. A nice bonus is that this sort of isolation lends itself to distributed and parallel designs, where the more lightly coupled remote objects are to each other, the better they work together. Besides, it's hard to convince your manager you need the latest fire-breathing multi-processor workstation if your design is hopelessly interlocked.

This blog entry is way too long. I hope however that it dispells the idea that writing a classloader is rocket-science or limited to a few obscure uses. Managing application versioning is a problem many application teams face, and some judicious classloading can make it much easier.

Remember, no rocket science, this. :-)

Tuesday, July 12, 2005

Eclipse craves for matisse

Eclipse users are desperate for an answer to netBeans' "Matisse" gui builder. But there's no solace and no respite in sight.
I want to hear the eclipse zealots respond to this one.
Here's the bug report that begs for it. (Sadistic pleasure, this.)
The old order changeth. :-)

Friday, July 08, 2005

Task queues in Solaris

Good blog on deferred processing of kernel level tasks in Solaris.

Thursday, July 07, 2005

MIT Blog Survey

Found this survey on one of the sun blogs. Will make for interesting stats, I'm sure -

Take the MIT Weblog Survey

Tuesday, June 28, 2005

Sun's truce with IBM (for now)

InfoWorld reports -
In detailing IBM’s renewal of its Java agreement, Schwartz acknowledged that there has been “a little bit of a chill” in the relationship between the two vendors. But IBM and Sun announced an 11-year extension to their Java technology agreement. IBM will continue to license Java technologies from Sun including the Enterprise, Standard and Micro editions of Java as well as Java Card technologies. IBM also will continue participating in the Java Community Process.

In addition, IBM will port its DB2, Tivoli and WebSphere middleware to Sun’s Solaris 10 OS. The agreement, said Robert LeBlanc, general manager of the WebSphere product line at IBM, shows that IBM and Sun are in Java for the long haul.


Peace - and prosperity - for the community

Positive reviews for the Sun App Server 8.1

Good review/comparison with WebSphere

Friday, June 24, 2005

Eclipse's "external" contributors

A good flame war on IBM's hypocritical claims of "the huge number of external contributors in Eclipse"

Friday, June 10, 2005

Good DTrace introduction (flash)

http://snpnet.com/sun_DTrace/dtrace.html

netBeans Abbreviations

You can add your own abbreviations (i.e editor shortcuts) to NB by creating/modifying the abbreviations.xml under [user-home-netbeans-dir]\4.1\config\Editors\text\x-java\.
Roman Strobl explains.

Thursday, June 09, 2005

NetBeans overtakes Eclipse

NetBeans has surpassed Eclipse on the Daily Traffic Rank Trend on June 7th.
Here's the evidence.

Contributing to the J2EE SDK (RI)

If the title interests you, this is where you should be headed - https://glassfish.dev.java.net

Wednesday, June 08, 2005

Sun CTO on divergent processor ISAs

Worth a read

Tree felling around Sankey

I was alarmed to see trees lining the walking-track around Sankey Tank being felled this morning. But, on enquiring at the Deccan Herald office, I found out that they're following a carefully laid-out development plan for the peripheral areas of the lake. A committee is supposedly in place to carry out the planning & implementation of this. The reporter was kind enough to provide details about this. He even said that 2 new trees will be grown in place of every OLD tree chopped there. But, when I asked him if such "committees" were not acting as per their own whims & fancies, he just said, "We're hopeful that that's not the case & that all this is not an eyewash.We're keeping an eye on developments".
In retrospect, the press is probably the last powerful institution that's capable of protecting Bangalore's ecological balance. (Sigh)

Tuesday, June 07, 2005

A Mac laptop running on Niagara?

Read this blog on ZDNet that goes like-

"So what can Apple do? What they should have done two years ago: hop into bed with Sun. Despite its current misadventure with Linux, Sun isn’t in the generic desktop computer business. The Java desktop is cool, but it’s a solution driven by necessity, not excellence. In comparison, putting MacOS X on the Sun Ray desktop would be an insanely great solution for Sun while having Sun’s sales people push SPARC based Macs onto corporate desktops would greatly strengthen Apple.

Most importantly, SPARC is an open specification with a number of fully qualified fabs. In the long term Apple wouldn’t be trapped again and in the short term the extra volume would improve prospects for both companies. Strategically, it just doesn’t get any better than that.

Niagara rocks. You want low power use for a laptop? How about an eight way 1.4Ghz SMP core with TCP/IP and cryptography done in hardware - at 65 watts flat out. There are some serious software issues, but get past them and you’ve got eight to ten Xeons in the box - at 65 watts.

Sun’s president, Jonathan Schwartz, put a nice invitation for you in his blog last Sunday. Maybe you should think about it..."


Sun folks(you need to pursue this...) & Apple folks, ARE YOU LISTENING??? Isn't this worth a try?

Monday, June 06, 2005

Sun:leading everywhere

Some really heartening developments :

1)The E20K kicks IBM's 32 way power 5 based server and one from HP that has a similar (but in the end - inferior) configuration.

2)The new netBeans gui builder simply rocks!!!

3) netBeans draws level with eclipse

4)Sun acquires storagetek to add to the procom IP acquisition and tarantella(on the secure,remote desktop front).

Hopefully, all these will see sun hitting back hard at its competitors in the quarters to come.

Tuesday, May 31, 2005

Why clone is slower than newInstance

Ken Russell from the Java HotSpot VM Group, Sun Microsystems writes in java.net forums:
"Actually in the HotSpot JVM Object.clone() is not currently heavily optimized, while new instance is. You can feel free to file an RFE about this in the bug database. You can work around this problem by overriding clone() and manually allocate the new instance and assign the data into it. In fact, I think this is already probably necessary once your data structures get more complicated, which is why slow performance of Object.clone() hasn't shown up on our performance radar.
P.S. Here's a revised version of your test case which gets rid of startup transients:"


public class Main {
private boolean flag = true;
private String string = "Hello";
private int i = 0;

static class Child extends Main implements Cloneable {
private boolean flag2 = false;
private String string2 = "World";
private int i2 = -1;
}

private static int count = 1000000;

public static void testClone() throws CloneNotSupportedException {
Child child = new Child();
int res = 0;
long startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
Child child2 = (Child) child.clone();
res += child2.i2;
}
long stopTime = System.currentTimeMillis();
System.out.println("" + count + " clones took " +
(stopTime - startTime) + " ms (dummy result = " + res + ")");
}

public static void testNewInstance() {
int res = 0;
long startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
Child child = new Child();
res += child.i2;
}
long stopTime = System.currentTimeMillis();
System.out.println("" + count + " new operations took " +
(stopTime - startTime) + " ms (dummy result = " + res + ")");
}

public static void main(String[] args) throws CloneNotSupportedException {
testClone();
testClone();
testClone();
testNewInstance();
testNewInstance();
testNewInstance();
}
}

Friday, May 27, 2005

Slashdot: In 72 Hours, Your Ban Will Be Lifted

Has this happened to you?
"Your Headline Reader Has Been Banned".Your RSS reader is abusing the Slashdot server. You are requesting pages more often than our terms of service allow. "Do Not Bother Contacting Us For 72 Hours".Very funny.
The faq page says this

Wednesday, May 25, 2005

Cartoon blogging

This is simply fantastic. This guy's got talent.

Friday, May 20, 2005

Thursday, May 19, 2005

Netbeans mobility pack ROCKS!!!!!

Sat through the Sun tech days session on writing MIDP apps with netBeans. Chuk-Munn Lee simply rocks!! Now, the NB mobility pack is an amazing piece of software -beautifully crafted and shockingly productive. It made me want to give up J2SE dev & take up J2ME full time. :p

More on the J2ME capabilities of NB here and here.