Thursday, December 18, 2008

Project Jigsaw #2

Alex Buckley has given a presentation at Devoxx in Antwerp last week (December 2008) that is the next step in the design of Java modularity. Alex has sent me the link to his presentation, you can find it on his blog.

This presentation is the next step in the saga of Java modularity. Sun’s history of Java modularity is littered with blogs and presentations; it shows no proper requirements and design documentation whatsoever. Neither has any of these blogs and presentations been discussed in, or presented to, the relevant JSR 294 or JSR 277 expert groups. It feels therefore awkward to react to a design that has no visible requirements nor a proper design document. How can one judge it, except highly subjectively? By its very nature, a presentation is giving an overview of an underlying construct, leaving out all the details that are so important to understand and evaluate a proper design. One can only hope that these design documents exist in Sun's offices somewhere.

However, getting modularity right in the Java platform can make a tremendous difference for hundreds of thousands of companies and millions of individuals. And clearly there is a self interest as well: it is of paramount importance to the OSGi Alliance to get it right. Because there are no discussions on the proper mailing lists, I feel forced to react with the same ill-matched tools of blogs and presentations. Excuses.

The Devoxx presentation begins with sketching the problems that JSR 294 addresses. These can be summarized as:
  • JAR Hell
  • Platform Fragmentation
  • Startup Performance
However, in the remaining parts of the presentation where the solution is sketched, many implicit requirements rear their heads. In the following sections I discuss the problems that I think Sun is trying to address and then address the areas that I think are not on their horizon.

JAR Hell is composed of the following problems (derived from Dependency Hell, DLL Hell, and JAR Hell):
  • (Too) Many Transitive Dependencies. This is related to the dreadful feeling you get when you want to use A, which needs B and C, which need D, E, F, G, ad nauseum. Though a certain amount of coupling is required to enable reuse and extension mechanisms, it often turns out that many dependencies are not always necessary. However, in Java there is no way to express this optionality.
    Many popular (open source) libraries have a staggering amount of dependencies. For example, use maven to compile "Hello World" for the first time. It downloads an impressive amount of libraries because it has no mechanisms to manage the unnecessary transitive dependencies. OSGi handles this problem by using the service model where only the packages with service interfaces are shared and picking packages, which have the smallest possible granularity in the Java VM model, as the unit of sharing.
  • Dependency on multiple versions. Java applications cannot have multiple versions of the same component in a single application. Well, why would you want this? Assume you rely on JAR A and JAR B. Both A and B require JAR C. Unfortunately, A requires version 1 of C and B requires version 2 of C. In Java you are out of luck. The reason is that they only have a linear class path so or C;version=1 comes first or C;version=2 comes first. During run-time, either A or B is bound to the wrong version, wreaking havoc at unexpected times and places. In OSGi, this problem is addressed by precisely wiring up bundles based on meta-data in the JAR manifest.
  • Unmanaged Dependencies. Java has no way to specify dependencies on other JARs. Depending on the class loader hierarchy, the class path, JARs in folders, and magic. This is all done "blind", there is no verification that it matches the assumptions in the JAR files. The effect is that errors happen (too) late and not early. Trying to make a set of JARs work together can therefore be a cumbersome and very hard to get right. In OSGi, this is addressed with the manifest in each bundle that specifies the assumptions of the code. This allows the framework to verify the bundle dependencies before the code gets started.
  • Use of private code. All public code in a JAR file is visible to all other JAR files, and public is pervasive because it is required to implement an interface. It is therefore easy to use code that is supposed to be an implementation detail, which can easily break a client in a later version or unnecessarily restrict the provider. In OSGi, this is addressed by explicitly exporting packages. All other packages are private.
  • Stomping. Stomping is the problem that you overwrite one JAR with another because the name is the same though the version is different. This can have very subtle, and much less subtle, unpleasant effects while everything looks OK. OSGi addresses this problem by having a clear install phase that separates the JAR from the internal structure. That is, a JAR file can have the same name but can both be installed in the same VM if they have different versions. This install phase connects very easily with all kinds of repositories and management systems.
Platform fragmentation is about the sad story of how Java fragmented into an incompatible set of profiles and configurations at the beginning of this millennium. Today we have Jave ME and Java SE that have grown largely incompatible over time. The argument for this fragmentation is that a mobile phone or embedded device could not be expected to run the same VM as a desktop. This is a valid argument but unfortunately the solution of profiles and configurations was not well thought out. The different profiles are not upward compatible which requires that a programmer targets a profile (for example Foundation Profile). This code will likely run into problems on Java SE because there are packages and classes that do not occur on Java SE, and of course vice-versa. Worse, packages often have different contents in different profiles and there are even differences as small as visibility, new or removed methods and fields. And worst of all, there is no meta-data describing what a JAR expects of its environment so that code can start running only to throw an exception somewhere in the future.

The key mistake made with the profiles and configurations was caused by the lack of modularity. If Java had been re-factored in a core VM (including java.lang because everybody must share the Class and Object classes) then sets of packages that could have extended this core VM. However, for some bizarre reason it was often not even legal to run a packaged developed in Java ME. The most bizarre JSR that I ever was involved with was JSR 197 that made it legal to run the javax.microedition.io package on Java SE. It is hard to believe that it took us a year to accomplish this minor, but important for one of the OSGi's specifications, feat.

In OSGi we came from the embedded world so this was a serious problem for us from day one. Obviously, we could not change Java itself, but at least we could address the sub-setting problem and the unmanaged aspect of it. We came up with the execution environments. An execution environment looks a lot like a profile/configuration but it is not intended to be the final word. It is a description of a set of classes that are a proper subset of all feasible Java environments, that is, the common denominator. Compiling against this subset (we have the Jars that contain only public API for you to do this) ensures that you are not coupled to anything outside the execution environment and as a consequence, outside any of the sub-setted profiles. For example, we have ee.minimum, which runs on all known profiles, from CDC/FP to Java SE 7. We use this execution environment to target all our APIs. We also have ee.foundation which is aligned with Java ME Foundation Profile. These execution environments are used by Equinox, Felix, and Knopflerfish to allow their implementations to run on the widest possible set of VMs.

However, we did not stop there. We also designed meta-data in the bundle's manifest that indicates the assumption of the bundle about its environment. A bundle cannot resolve unless the framework can establish that the VM implements one of the required execution environments.

Performance. This problem is related to the work done in Java 6 where the JRE can be incrementally loaded to reduce start-up time. This speedup was necessary because it basically made applets impossible to use because the page froze for up to a minute the first time an applet got started. There are two strategies involved in improving performance: lazy and eager. In a lazy model, the code is not activated until there is a direct need. This model works very well where you have a large application where many parts are rarely used and loading code is relatively fast (local disk). Eclipse is a good example where lazy loading is used heavily to minimize startup and footprint. Eager loading is better when it is clear you will need it soon and loading code is slow (over the net). A good example is the average applet. There are many variations and anybody ever written a serious cache manager knows the trickiness. Performance from a module system therefore depends on the available meta-data and initialization/activation model.

This problem is further clarified later in the presentation when it requires that a module should be able to contain partial packages to speed up downloads: java.lang is split in 3 different modules. This requirement is begging for more complexity and the payoff seems very slim. Splitting packages along performance boundaries will require great foresight to have any performance effects and will in almost all cases be in conflict with minimizing coupling. It is a classic example of coupling of two unrelated concepts (performance, low coupling) into a single design concept (module). In practice, it always results in systems that are not good in either performance nor in decreasing the coupling.

Though performance is a crucial aspect of the VM (and some fantastic work has been done in Hotspot, even without modularity), it is important not to mix concepts that have very different optimization axes. Every time when I see that happening, both axes have to be compromised.

Integration with native packaging systems. One of the not well defined concepts in the presentation is the integration with native packaging systems. A typical native packaging system is rpm. Packaging systems use dependency graphs and scripts to modify an operating system to a new state where it provides new functionality. There is a tremendous experience in packaging systems and nowadays they are quite impressive in how reliable they work.

However, take one step back. The absolute number one value of Java is its platform independence. Native packaging systems should be able to reliably provide modules to the Java platform, but I am fairly confident that inside Java we do not want to see any of these native systems in Java. It is crucial that the Java module system is a well defined system in Java without having to defer to a platform dependent module system. This would be the anathema of Java. However, the presentation seems to assume that native packaging system would integrate with the VM? For example, module version syntax is not defined so it can leverage formats from packaging systems. Alas, if we only had a design document and not a presentation ...

Updated 1: Alex Buckley has told me that Project Jigsaw nor JSR 294 willprepare for native packaging systems ... So my fear is unjustified.

Package granularity. Package granularity has always been a hot potato in Java. Though packages look like a first class citizen, there is a lot of fudging going on to ignore them. On one side we have a Package class representing a package, we import packages in our source code, and there are clear access visibility rules for classes in the same package. However, in the class file format a package is not visible and this has led many (including the JRE) to treat packages as second class citizens.

In the presentation, it is argued that libraries often consist of multiple packages. Though the OSGi service model shows that constraining the interface to a single package works well for highly decoupled designs, it does make sense to be able to think about a group of packages as set that belongs together. This is for me the conceptual advantage of a module: a group of packages that tightly belong together. Superpackages anybody?

Multiple module systems. There seems to be a strong implicit requirement that there will be multiple module systems in the VM. These module systems are somehow supposed to handle the run-time class loading in different ways. Sun will provide a "simple" module system for the JDK, but it will allow others for the application level. They are even polite enough to not specify a version syntax so that OSGi can use its own version syntax while Sun can continue with versions that always start with 1.

Multiple implementations of a specification sounds good, doesn't it? Hmm. Let's look what it means. It means that programmers will have to choose a deployment format because none is specified. As a programmer I will have to choose one or more formats I support because I cannot waste the resources to support all. Do I have any gain as a programmer by having a "choice"? Nope, every choice I make makes my code incompatible with modules from other systems.

Specifications are supposed to simplify the life of programmers, not make it harder. By not having an open discussion about deployment formats and creating consensus around one format, the problem is dumped on the lap of millions of programmers, creating confusing and chaos where none should be necessary. Even if interoperability is supported, there will be lots of small problems for no obvious reason.

And there is of course a political aspect. Sun moved the deployment aspects to an OpenJDK project called Jigsaw. The scope of Jigsaw is to create a module system for the JDK and applications. Though I am fairly sure it will be not match OSGi's capabilities it will be part of the JDK. It is hard to ignore the similarity between Microsoft including Internet Explorer in their operating system because they could not compete on functionality with Netscape.

Missing Aspects
The following section details problems that I thought were an intrinsic part of a module system but that are not discussed in the presentation. I think these areas are very important and closely related to Java modularity.

Class Space Consistency. One of the hardest parts in the OSGi R4 specification was class space consistency. Once you allow multiple versions of the same package in a VM you must ensure that the different modules use the right class loaders or you get hard to diagnose class cast exceptions. That is, a class X from class loader A is not compatible with a class X from class loader B. Confusing but true. In OSGi we have the concept of a class space and we maintain consistency in this class space using the "uses" directive, providing information of what implementation dependencies a package has. With this directive, a framework can assign bundles to different class spaces and thereby ensure no collisions happen. The presentation explicitly acknowledges that this can happen in the proposed model, but does not propose to fix this.

This might not be a major problem for a JRE where it is likely that all modules are only a bug fix away from each other. By definition, a JRE is only depending on itself. It is not very likely that a JRE will have the problem of multiple versions of the same packages. However, application programmers that use a large number of open source libraries are rarely that lucky.

Supporting multiple versions in one application is one of the core aspects of JAR hell. Enabling this is therefore good. Not guaranteeing class space consistency will only create module hell.

Plugin/Extensions. The largest missing area in the presentation is a plugin extension model. One of the primary reasons to choose OSGi is to provide an extension model but the presentation assumes all modules are statically wired. Implicit in the model is that we'll be stuck with class path scanning (OK, module scanning) and class loading hacks to make today's applications.

Compatibility. Java is clearly backward compatible and I applaud Sun for the remarkable feat that Java 1 code can still run on a Java 6 VM. However, there is also forward compatibility and that is largely lacking from the JDK perspective. Most javax packages can easily run on earlier VMs. For example, the javax.script package defines a way how script engines can make themselves available to application code. There is nothing in this package that would make it impossible to run it on a Java 1.2 VM. However, it is only available in Java 6. If you can always run the latest VM (I guess like when you are a Sun employee) this is hardly a problem. However, for the rest of us it does pose a problem to move our code bases to the next version, which usually causes a lag of 2-4 years.

Project Jigsaw in OpenJDK and JSR 277 target Java 7, which is supposed to be out in 2010. How could people on older VMs take advantage of some of the features? Is modularity really only needed on future VMs? Looking at OSGi it seems unnecessary to only focus on the future VMs, it runs as well on Java 7 as on Java 1.2.

Preliminary Conclusion
The presentation unfortunately shows all the aspects of too few eyes from too few perspectives. The interests from the VM perspective have a huge role in the presentation. However, the application programmer's perspective has been more or less ignored.

And then there is the most important aspect of all: multiple module systems. Java is in the extremely fortunate situation to have only one modularity standard today that is well adopted and highly mature. Analyzing the problems as stated in the presentation I have not seen anything that OSGi could not do better today. Though in any other area competition is good, a module system is the technology that allows parties to compete on better implementations without technological friction. A single module system will reduce the ease with which one can adopt open source libraries or commercial components. The world really does not need more than one module system in Java.

I am hoping that Mark Reinhold and Alex Buckley will bring their requirements to the OSGi Core Platform Expert Group where we could discuss the problems and have more (some very experienced) eyes on what is really need. I am pretty sure we can find a consensus. I actually hope we can do this soon: there is an OSGi Expert Group meeting in Boston in January and Sun is a member.

I am fairly sure there is only one requirement we can unfortunately not address: "There shall be no OSGi technology in the solution".

Peter Kriens

8 comments:

  1. Modularizing the JDK is a question that's driven by the need to make the JDK itself easier to deploy across many different *native* deployment environments, RPM, dpkg, IPS, 'none at all' - you name it.

    Universal OSGi does not exist today, as you have elaborated yourself on this blog a while ago, so OSGi as it is right now does not really work very well as a way of making deployment across *native* environments easier - it currently requires a JVM, which would pose some obvious bootstrapping challenges: if a user needs to run Java to be able to run OSGi to be able to install the JDK, then something is quite obviously wrong with that picture - JDK 7 needs to be easier to deploy then JDK 6, not harder. ;)

    Jigsaw decouples the concrete implementation used to split and reassemble the JDK itself into components, from the abstract, language level facilities used to enable that to happen. That's an overdue idea in times of ubiquitous native package management facilities like RPM, dpkg, IPS, that scale to tens of thousands of interdependent packages and millions of users. With Linux and now OpenSolaris making inroads on netbooks and other environments with limited resources, and with no chance of any versioning ideology 'winning' against more flexible and suitable solutions any time soon, it makes a lot of sense to not have to have a rerun of the amusing 'three version numbers good, four version numbers bad' debate with open source software distributors who have built systems that work with arbitrary many ones, and have done so for the past 15 years.

    What makes a lot more sense instead is to give the JDK developers through JSR 294 a means at hand to get out of that JAR Hell more easily by allowing the module metadata to be embedded with the code, without mandating a versioning syntax or semantics. That would allow the JDK itself to be modularized employing native systems underneath to do so, while giving a future revision of OSGi the means to work with a richer set of modularity related metadata embedded right in the source & bytecode.

    The Jigsaw design does exactly the opposite of the weird Microsoft analogy you're mistakenly trying to draw: it leaves it to the competitive space above the JDK layer to sort out the battle for developer attention among Ivy, Maven, OSGi, etc. without forcing any of them to have to adapt to the requirements below the JDK level, or mandating any of them as the standard way of doing things.

    The JDK has actually been modularized in a rudimentary fashion by distributors ever since the OpenJDK code base was released. Ubuntu today splits it into eight packages, three of which cover the JRE. They use dpgk to do that, and it just works.

    The Fedora team uses RPM to split it into different packages, and it just works, too. The OpenSolaris team uses IPS to split it into different packages. What they all need is a way to split the JDK down even further, into specific modules of the class library, that's consistent across JDK distributions, and that does not necessarily require a JVM.

    And that's the part of picture many OSGi advocates are missing, since they are familiar with the application modularity domain, rather then the JDK distribution problem domain. Those are really two different problems - for the latter one integration with existing native deployment facilities is crucial, if one wants to make JDK deployment easier for users, developers and distributors alike.

    ReplyDelete
  2. It seems that Apache Harmony already is modularized with OSGI, so modularizing a JVM with it seems to be possible: http://www.backtype.com/url/neilbartlett.name%252fblog/comment/16002146

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Let me rephrase that. ;)

    Something being possible doesn't mean it's therefore necessary to adopt it without further consideration.

    What the Harmony project has shown, is that you can use OSGi in a suitably modified VM, if you try hard enough. That's a nice technical accomplishment, no doubt about it.

    There are many ways to modularize a JDK. Most of them don't involve Java at all.

    I implemented a basic way to have class library profiles for Kaffe back in 2003/2004, for example. It turned out to be a pain to maintain that no one among my users really needed, so it got kicked out a few years later by me, again. That was before Harmony and OpenJDK projects existed.

    I've spent part of the December of 2005, after we bootstrapped Harmony, chatting with Tim Ellison (IBM, Harmony), and GNU Classpath developers how to make cross-JDK modularity work. Since IBM was involved in Harmony, OSGi made sense to them, but GNU Classpath developers had a bunch of pretty good arguments why OSGi wasn't necessarily the ultimate solution, bootstrapping problems included. They also gave them to Peter, who about then discovered GNU Classpath himself, and came to the mailing list to approach a similar topic. It's pretty easy to find those things on the public web, if you ask mail archives, btw.

    So, fast forward to a few months later, and you should be able to find posts from Glyn Normington on the Harmony lists explaining the same hairy bootstrapping and visibility issues to Harmony developers. I think Glynn and team eventually moved on to implement a subset of OSGi within DLRVM, but at that point I was no longer actively following the project myself, so there are much more knowledgeable people who can explain what was done in the end, and why that was the way things had to be done, and what that means for things like startup performance.

    The point is - even across open source implementations of the VM and the class library, before OpenJDK, use of OSGi to break up the class library was not in any sense a given, even before 277. It still isn't today, since at the level below the JDK, things are quite different.

    IKVM, for example, an implementation that sits on top of the .NET framwork, and uses the OpenJDK class library, is in the process of splitting it into modules using .NET assemblies, as avid readers of planetjdk.org know. For that use case, .NET assemblies make a lot of sense.

    The distributions I cited above are doing the same using their own means, their native packaging tools. For their use case, being able to statically put well-known modules together using native packaging tools, regardless of the programming language they were written in, is a lot more important than other features.

    ReplyDelete
  5. Dalibor,
    I think your reply illustrates one of the issues I see. The focus seems to be very much on modularizing the JDK and "hoping" that the resulting module system is useful for applications.

    There are very few companies making VMs, shouldn't it be better if we also take the needs and requirements of application developers into account before we create something that might be simple for Sun but simplistic for developers? Everything you say is relevant and clearly important from the JDK's perspective. However, there are many more things to consider that I feel are currently not taken into account because there are no open discussions taking place between the stakeholders in an appropriate forum.

    About the Microsoft IE versus netscape metaphore: notice that Mark Reinhold indicates in his blog that the module system for the JDK is also useful for applications developers: This module system will be available for developers to use in their own code, ... How can one compete with an embedded module system? Do we want an eco system that competes with module systems? It seems a module system should simplify competition by reducing friction, not make it more complex?

    The only thing I am asking for is gathering the requirements from all the stakeholders so we can get multiple perspectives before irreversible design decisions are made and become part of the VM. Isn't that the whole purpose of an Expert Group?

    Peter Kriens

    ReplyDelete
  6. I was involved in prototyping some VM extensions, although not in the DLRVM, to enforce the subset of OSGi used by the Harmony class libraries. The exercise showed that OSGi metadata was sufficient for modularising the class libraries, although we did not investigate packaging the JVM using native package managers or exposing the VM-level OSGi support to applications.

    There seems to be a lack of clarity over the objectives of Project Jigsaw. If it starts as an internal technology for modularising the class libraries and supporting native package managers but ends up showing through to application developers, it'll have repeated much of the JSR 277/294 fiasco with its NIH attitude to OSGi.

    Perhaps this could be avoided by adopting a more open and collaborative approach from the start...

    ReplyDelete
  7. This comment has been removed by a blog administrator.

    ReplyDelete
  8. This comment has been removed by a blog administrator.

    ReplyDelete