|
|
||
Ethan Nicholas's BlogApril 2006 ArchivesLeaking EvilPosted by enicholas on April 26, 2006 at 04:39 PM | Permalink | Comments (15)Memory leaks in Java are fortunately pretty rare, and when they do exist, off-the-shelf tools like JProfiler and OptimizeIt normally make short work of them. So when I discovered that an application I'm working on had a memory leak -- a BIG one -- I wasn't too worried. At first. Should just be a simple matter of spending some quality time with a profiler, shouldn't it? This particular leak, though, seemed to defy all effort to actually catch it in the act. It never happened while I was profiling it, and for that matter it seemed to never happen while I was actually paying attention to the application. I would spent two hours unsuccessfully trying to make the leak happen, walk away from my computer for ten minutes, and come back to find an additional five megabytes of memory gone. And when I tried the same thing (so I thought) in a profiler, it didn't happen. As it was such a big leak, megabytes at a time, I knew it had to be image-related -- there just isn't anything else that leaks in such big chunks, and this particular application indeed processes gigantic images. I carefully checked my usage of all such images, and never found anything the least bit problematic. The bug was a ghost, striking only when my back was turned. After a couple days of dedicated hunting, I finally managed to catch the leak in a profiler. Sure enough, it was a five-megabyte int[] which belonged to an Image. But, curiously, it wasn't my image -- it belonged to RepaintManager. I felt a sinking sensation in the pit of my stomach as I realized it was a double buffer image. Ten seconds later, I was looking at Bug 6209673: Memory leak repainting Swing UI caused by non-java process using 3D graphics. The summary is misleading, as it has nothing to do with 3D graphics. What actually happens is that any time your computer changes screen resolution (and therefore GraphicsConfiguration) and a paint occurs, the RepaintManager allocates a new double buffer for the new GraphicsConfiguration. And keeps the old one. Other than the obvious "manually changing screen resolution" mechanism of doing it, there are a lot of other things that will trigger this bug. Screen savers or games might change the resolution. Hibernating your system will generally result in a new GraphicsConfiguration, so a Swing program running on Java 5.0 can leak megabytes of memory every time the system is hibernated. In some cases you can defend yourself by never repainting in the background: by never painting while the screen saver or game's GraphicsConfiguration is in effect, you prevent Java from allocating a new buffer, but this isn't always practical and doesn't defend against all occurrences of the bug. In my case, I'm on a laptop, so hibernation was the culprit. That is also the reason I could never get it to happen while I was actively looking for it -- why would I hibernate my laptop while I'm in the middle of hunting for a bug? The workaround (subclassing RepaintManager and changing the caching code in getVolatileOffscreenBuffer) was straightforward; the real difficulty was in tracking the bug down, because by its very nature it doesn't happen while you're actively looking for it. Since this bug is pure evil to find, can affect any Swing program, usually leaks five or more megabytes at a time, and has a very misleading summary, I think it's important to be aware of it. Hopefully you'll at least figure it out faster than I did. Reinventing GridBagLayoutPosted by enicholas on April 20, 2006 at 10:51 AM | Permalink | Comments (13)Java suffers from a layout crisis. It ships with a bunch of poorly-thought-out layout managers, a few (like GridLayout and BorderLayout) that are decent within their limited niche, and... GridBagLayout. Despite a few warts, GridBagLayout is powerful enough to handle almost any layout task -- but it's so ridiculously difficult to use that most programmers avoid it like the plague. There have been many attempts to replace GridBagLayout, with varying degrees of success (TableLayout, SpringLayout, GroupLayout, and FormLayout, just to name a few) but from what I have seen, most people just drop back to using simpler layouts, like BoxLayout, in nested panels. This is a bad idea. For one thing, it complicates component setup and makes rearranging the UI and controlling resizing behaviors much more difficult. For another, it results in many additional components. A setup that could be done with a single GridBagLayout might take four or more panels with simpler layouts -- and every extra level of nesting is another level that has to be processed during repaints, event handling, and layout, not to mention the unnecessary memory consumption. Memory and performance are already at a premium in Java, so you can ill-afford to create more components than necessary to get the job done. GridBagLayout isn't fundamentally a bad layout manager -- it's just too hard to use. If it were simpler, I don't think most of the "GridBagLayout replacements" would have a reason to exist, as there is very little that GridBagLayout can't do. While building the JAXX user-interface language, I needed a powerful, easy-to-use layout system, and (perhaps surprisingly) I settled on GridBagLayout. JAXX offers a Table component which is built on top of GridBagLayout. I briefly mentioned Table in my introduction to JAXX, but didn't go into any details. The goal of the <Table> tag was to offer all of GridBagLayout's power, while removing the tedium and error-prone-ness (error-prone-itude?) that characterizes more typical GridBagLayout usage. Take a look at a simple table:
Here's what it looks like when run:
GridBagLayout isn't perfect, of course -- I needed to use a sub-panel to get the buttons right -- but that was sure better than coding GridBagLayout by hand, wasn't it? JAXX's Table tag offers a lot more features, like the ability to specify GridBagConstraints attributes on each <cell>:
You can also specify constraints on a <row>, in which case they apply to each cell in the row (unless overridden). You can even specify default constraints for every cell by putting them on the <Table> tag itself:
Table-level defaults can be overridden on either a per-row or per-cell basis. I think that JAXX's <Table> tag breathes new life into the much-maligned GridBagLayout, allowing it to flex its power while removing (most of) the cursing that using GridBagLayout typically entails. Eventually JAXX will offer specialized support for other advanced layout managers, like FormLayout, but for the time being GridBagLayout is surprisingly powerful when it isn't hampered by the manual constraints wrangling of days past. For more information on JAXX, take a look at www.jaxxframework.org. Creating a Service Provider InterfacePosted by enicholas on April 07, 2006 at 09:04 AM | Permalink | Comments (7)While working on my XML user interface language, JAXX, I wanted to provide a mechanism for users to add support for additional tags and data types. As JAXX is a command-line compiler, a run-time API to add additional features would be very awkward to access, so a Service Provider Interface (SPI) seemed like a natural fit. In case you aren't familiar with SPIs, they have a tantalizing proposition: just put a special JAR file somewhere on your class path, and the features in the JAR file will be found and made available. Many of Java's internal APIs, such as JNDI and javax.imageio, have SPIs, which makes it a snap to plug in new features. So, great, I needed to write an SPI. I spent some time wrestling with search engines to find documentation on how to actually create an SPI, and found nothing. Tons of information about how to interact with the SPIs built into Java, but absolutely nothing about how to create one. There was nothing left to do but dig through Java's source code to figure out what Sun was doing, so I could shamelessly copy it. The answer turned out to be embarrassingly simple. The method ClassLoader.getResources() returns an Enumeration of all resources with a given name, like myspi.properties. To implement an SPI, you have each SPI JAR contain (say) a properties file with the same name in the same location. When you use ClassLoader.getResources() to get a list of all such properties files, you will find one properties file for each JAR on the class path which implements your SPI. The properties file should contain whatever information is necessary for you to make use of the features in the JAR file. This typically takes the form of properties set to the names of classes found in the JAR file, which can then be loaded and used. In the case of JAXX, the properties file is named jaxx.properties and is located at the root of the JAR file. It contains a single property jaxx.initializer, which should be set to the name of a class in the JAR file. This class must implement the interface jaxx.spi.Initializer. JAXX can then, via the magic of ClassLoader.getResources(), easily find all such initializer classes and load the support they contain. Presto! A fully-functioning SPI, just like the ones Sun uses. Java 2 Browser EditionPosted by enicholas on April 04, 2006 at 05:29 PM | Permalink | Comments (48)The problemIn my day job at Yahoo!, I face a frustrating problem: Java is the most powerful browser-based technology available, easily besting competitors like Ajax and Flex, and yet I can't use it. These are the main reasons: It's too big - Sun is (rightfully) proud of the fact that 90% of computers already have Java installed, but that still means that 10% of my users are stuck having to download a 7MB plugin. They might be willing to do it for a compelling enough application, but they definitely won't do it for something that could just as easily have been done in Ajax or Flex. It's too slow - Sun has been working hard on Java's performance, and it shows. Once it gets up and running, it runs rings around every other rich client technology. Flash, for instance, is pitifully slow compared to Java. But the problem is that bit about "once it gets up and running". It can take thirty seconds or more to cold-start the JVM on an average desktop machine, compared to "instant" for both Ajax and Flash. A user might -- might -- tolerate that once or twice. They certainly won't tolerate it over and over again, which makes Java applets a good way to lose repeat visitors. It's too hard to install and upgrade - Compare Java's installation process to Flash's installation process. I think that's all I need to say here. It's too unreliable - I have seen a number of computers that simply won't run Java in the browser. You can uninstall Java, reinstall it, uninstall and reinstall the browser(s), and it still won't run applets. It just sits there, making no visible attempt to start the applet. If I, as a Java developer, can't figure out why such-and-such computer won't run Java applets, do I want to rely on my users being able to figure it out? I have never seen a system that inexplicably wouldn't play Flash movies or run JavaScript.
The solutionIf Java were able to offer an experience comparable to the Flash plug-in, Yahoo! would be using it all over the place. I'm sure the same goes for a lot of companies. If Macromedia Flex, which is a miserably buggy and downright awful product (at least as of version 1.5), can gain the kind of attention it has, I don't believe it's too late for Java to make a resurgence in the browser. It won't be easy, though. Here's what I want to see:
A 1MB downloadThere is of course no way the entire JRE can be crammed into 1MB, no matter how good the compression is. And yet I think it can be achieved. The most important part will be segmenting up the JRE into chunks which can be (automatically) downloaded and installed separately. I very much doubt that most Rich Internet Applications need JNDI support, for instance, or the ability to do XSLT transformations. By carefully choosing the core set of features to bundle, the JRE size could be drastically reduced. Likewise, I'd be happy with a simpler virtual machine -- as long as it's substantially faster than Flash (which even a Java 1.1-era JIT could easily manage), it's good enouch for RIAs. The goal would be to have your application specify the set of features it needs: Swing and XML parsing support, for instance. If the plug-in already had those features installed, great. If not, it would automatically and seamlessly go download them. If the plug-in wasn't installed at all, you would be directed to an installer which contained those (and only those) features. Ideally the installable features would be fairly fine-grained, keeping in mind that a small download is absolutely essential for success. Ideally, J2SE would be exactly the same as J2BE with all of the optional features installed. But if it really became necessary, I would be okay with having J2BE being a subset of J2SE, along the lines of J2ME. I don't need (or even necessarily want) all of Java's features in a browser plug-in. As long as there is complete upwards compatibility (all J2BE classes work unmodified under J2SE) and partial downwards compatibility (J2SE classes work as long as they don't use any unsupported APIs), I think it's an acceptable trade. Instant startupFor a long-running server application, Java's start-up time is essentially irrelevant. For a traditional desktop application, it's annoying but not the end of the world. For a typical short-lived browser-based application, though, it's murder. In every usability study I have ever attended, one fact comes through loud and clear: users are impatient. They don't like to read, they don't like to think, and most of all they don't like to wait. It sucks, but it's a fact of life and we as software developers need to live with it. To be competitive, Java applications must be able to start up almost instantly. I'm willing to accept them being a bit slower to start than Flash applications, but only a bit. A better installerIt must be as fast and as easy as Flash to install. Likewise upgrades should be seamless -- if an applet requires a higher version of the plug-in, the user is notified, the new version is downloaded and installed with no further messaging, and the applet starts. End of story. Improved reliabilityEvery customer that can't use my tools is a customer I'm going to lose. So if I'm going to commit to a browser-based technology, it had better be reliable. Damned reliable. Is there still hope?Maybe. There is no question that the idea of Java in the browser is currently dead, but I don't think it's too late for a comeback. Java is so much better than competing technologies that it's almost absurd. If Sun were to deliver on these features, I would switch to using Java in the browser in a heartbeat. I suspect that goes for a lot of you, as well. I don't think the question is so much can Sun win the war, but are they willing to? That, my friends, is a question I can't answer. | ||
|
|