`
java-mans
  • 浏览: 11414395 次
文章分类
社区版块
存档分类
最新评论

SWT: The Standard Widget Toolkit -- PART 2: Managing Operating System Resources

 
阅读更多

SWT: The Standard Widget Toolkit

PART 2: Managing Operating System Resources

The second in a series of articles about the design ideas behind SWT.
Summary
SWT uses operating system resources to deliver its native graphics and widget functionality. Allocating and freeing operating system resources is traditionally an area of programming that is error prone. Languages that include garbage collection, such as the Java™ language, relieve the programmer from the burden of managing memory, but not from the allocation and freeing of operating system resources. This article discusses the simple strategy used by SWT to help application designers manage operating system resources.

By Carolyn MacLeod and Steve Northover, OTI
November 27, 2001


Simple rules to help get it right!

When programming in a GUI operating system, you allocate operating system resources for widgets, images, fonts, and other graphical objects. Since there is a platform limit on the amount of resources you can allocate, you must be careful to free any objects that you allocate in your application. If you allocate a resource and do not free it when you are done with it, your application is "leaking" resources. An application that repeatedly leaks resources will eventually consume all of the available resources, forcing the user to reboot the operating system.

Fortunately, SWT makes resource allocation and disposal a straightforward process. There are only two rules that you need to remember when allocating and freeing SWT resource-based objects:

Rule 1: If you created it, you dispose it.

This is a simple rule.SWT makes it easy for you to remember when operating system resources are allocated: all SWT resource-based objects (like Color, Cursor, Display, Font, GC, Image, Printer, Region, Widget and subclasses) allocate any needed operating system resources in their constructor. There are no exceptions to this rule. There are no methods in SWT (other than constructors) that allocate operating system resources that the programmer must manage. If you didn't call the constructor, then you don't need to free the resources, so don't call dispose on the object. For example, in the following line of code, an operating system font is allocated:

Font font = new Font (display, "Courier", 10, SWT.NORMAL);

Since you called the Font constructor to create the resource, you must dispose the font when you are finished with it, as follows:

font.dispose();

In the following line of code, however, a constructor is not called:

Font font = control.getFont ();

Therefore, you must not call dispose. The font variable does contain an operating system font resource, but you did not allocate it. If you were to dispose of this font, you would be leaving the control without a font! The results are undefined. So, if you are using any getter that returns an SWT resource-based object that you did not allocate, do not dispose the object.

This rule occasionally leads to API that seems artificial. For example, GC.getClipping (Region) forces the programmer to create a Region in order to get the clipping region from a GC. Although it might have been a bit "prettier" to provide GC.getClipping () that returned a Region, this would break the rule because it would have to allocate an operating system resource outside of a constructor. While it could be documented that the programmer needs to free the Region returned by GC.getClipping (), programmers don't always read the documentation. Making the programmer call the constructor for the Region makes it clear that it is the programmer's responsibility to free the resource.

Rule 2: Disposing the parent disposes the children.

When you dispose a Shell, its children are disposed. In fact, disposing any Composite will dispose all of the Composite's children. Disposing a Menu disposes all menu items. Disposing a Tree or TreeItem disposes all child items.

Why does this make sense? Apart from the obvious burden of having to dispose each child if this were not the case, the fact that a widget cannot exist in the operating system without a parent implies that when the parent is disposed, the child must also be disposed.

What about the fonts and colors that you created and set into a Widget? Since they are not children of the widget, they are not disposed when the parent is disposed. A widget will never dispose a resource that you allocated. To do so would mean that the widget was breaking rule 1. Because fonts and colors can be shared by different widgets, disposing a resource in one widget would dispose it in another, leading to unpredictable results.

Extensions to Rule 2

There are two extensions to rule 2. These are places where a relationship exists that is not strictly a parent-child relationship, but where it still makes sense for rule 2 to apply.

MenuItem.setMenu: Disposing a cascade MenuItem that has a submenu set with setMenu (Menu) disposes the submenu. This is a natural extension of rule 2. It would be a burden to the programmer to dispose each individual submenu. It's also common behavior in most operating systems to do this automatically. Both Windows® and X dispose submenus when a cascade menu item is disposed.

Control.setMenu: Disposing a Control that has a pop-up menu set with setMenu (Menu) disposes the pop-up menu. Many application programmers expected this behavior, even though many operating systems don't do this automatically. Leaving the application programmer responsible for disposing a pop-up menu when disposing the Control led to temporary leaks. (The leak was only temporary because the pop-up menu is eventually disposed when the shell is disposed).

Why free at all?

The operating system frees all of a program's resources when the program exits. Why not just rely on this? Operating system resources are not infinite. If your program doesn't free up resources as they are no longer needed, it can run out of resources. It can also cause other programs to run out of resources. So waiting until the program exits to free up resources is generally a bad idea. Leak tools exist to help detect resource leaks. You can use the Sleak tool to look for leaks in your SWT application.

The Java language has Garbage Collection - Why not use it?

The Java language successfully uses a garbage collector to manage the memory used by Java objects. Why doesn't SWT use the Java garbage collector to manage operating system resources too? Many Java programmers ask this question. We will answer it with the following discussion on finalization.

Finalization

Most attempts to use the Java garbage collector to manage operating system resources involve finalization. When an object is about to become garbage, the garbage collector sends it the finalize message. This seems like a logical place to free the resource, right?

Conventional wisdom says that managing operating system resources in finalization is difficult and error prone. To quote Joshua Bloch in his "Java Series" book Effective Java,

"Finalizers are unpredictable, often dangerous, and generally unnecessary. Their use can cause erratic behavior, poor performance, and portability problems." [JB, 2.6].

One of the main reasons that this statement is true is that there is no guarantee how soon after an object becomes garbage that the finalize method for the object will run. Even if you could predict this time interval for a particular VM, you certainly couldn't guarantee it for new versions of the VM. Worse still, if there is plenty of memory or if an exception occurs in an object's finalize method, there is no guarantee that the object will be finalized at all! All of this is stated right in The Java Language Specification:

"The Java programming language does not specify how soon a finalizer will be invoked, except to say that it will happen before the storage for the object is reused. ... If an uncaught exception is thrown during the finalization, the exception is ignored and finalization of that object terminates." [JLS, 12.6].

Releasing operating system resources is critical to correct program execution. Freeing resources at some unknown time in the future (or not at all) typically leads to program failure. Here are some examples of typical problems encountered when using finalization:

  • If a non-garbage object references another object, that object is not garbage and won't be collected. If the object has operating system resources that it frees in its finalize method, those resources will never be freed because finalize will never be called. This is a classic programming error because it's easy to hang on to objects accidentally in an inner class or a static variable.
  • It is possible to starve other operating system processes by creating lots of "garbage resources" that have not yet been finalized. When another program attempts to acquire a resource, it fails because it cannot run the Java finalizer. SWT could run the finalizer if it couldn't get a resource, but other programs (i.e. non-Java programs or even Java programs running in a different VM) - and operating system calls that allocate and free resources internally - can't run the Java finalizer.
  • Some resources are very limited. For example, only a small number of cached HDCs are available on Windows 98. It is essential to free these up right away, instead of waiting for finalization.
  • Different types of resource are shared on the same operating system heap, so a failure to acquire one resource may be caused by failure to free another. For example, on Windows 98, HBRUSH and HFONT are both allocated from the GDI heap, so failure to create an HBRUSH can happen if too many HFONTs are awaiting finalization. This can cause applications to have some strange dependencies on the timing of finalizer runs.

The non-deterministic nature of finalization is sufficient reason to avoid using it to free resources, but even if we decided to go against conventional wisdom and implement a resource management scheme using finalization, we would still face many other problems. The implementation would require the addition of some very complex, platform-specific code that would be difficult to get right, and tough to debug. Here are a few examples of some of the tricky issues that would need to be carefully coded around in each object's finalize method:

  • Operating system objects can have subtle limitations on when particular objects can be disposed. For example in the X Window System, a GC can contain a Font. If you dispose the Font before the GC, you leave the operating system in an indeterminate state. The following quote from The Java Language Specification hints at the complexity of the code that would be required to handle such limitations for each object finalizer:

"The Java programming language imposes no ordering on finalize method calls. Finalizers may be called in any order, or even concurrently." [JLS 12.6.2]

Add to this that for some operating systems, subtle platform behavior in this area may be undocumented and can change in future versions of the operating system.

  • On some platforms, operating system resources need to be disposed in the UI thread. According to The Java Language Specification:

"... the language does not specify which thread will invoke the finalizer for any given object." [JLS 12.6]

This means that SWT finalize methods would need to synchronize with the UI thread, which would mean that there would be a significant cost overhead for each dispose. In addition, given the previous point that disposal order is important for some resources, we think it is possible to get into a deadlock situation if we are trying to reorder multiple concurrent threads that are trying to synchronize with the UI thread.

  • The Java objects that model operating system objects need to capture all of the subtle relationships between the operating system objects. For example, on Windows, an HBRUSH can be referenced in the operating system by an HDC, but not referenced in Java code. When the Java object representing the HBRUSH becomes garbage, freeing it will cause unexpected results. In fact, it's necessary to break the reference from the HDC first before attempting to free the HBRUSH. If these relationships are not captured and managed, it is possible for a resource to be finalized when there are still references to it in the operating system.
  • Freeing some resources automatically frees others. For example, on Windows, destroying a parent HWND automatically destroys the children HWNDs. On some operating systems, freeing a top level window also frees the icon associated with the window. Different rules for freeing resources need to be custom coded on a per platform basis.
  • Freeing resources can be time consuming, so a critical memory allocation may need to wait until finalization is complete.
  • Long waits in finalizers can also make it difficult to get good benchmarks when you are trying to performance tune your code.

Our final point against finalization is this: SWT keeps as much state in the operating system as possible to simplify the implementation (less code, smaller objects). If we were to use finalization, we could not store resources only in the operating system. We would have to hold on to them in Java code so that they would not get garbage collected. For example, when you set a Font into a Button, SWT sets the font resource in the OS button. The Button doesn't keep a reference to the font resource, because it doesn't need to. The state is in the operating system. In order to determine the Button's font, we query the OS. There is no point caching the font resource in Java code, because it just takes up space and complicates the Button code, which now has to make sure there are no stale cache problems.

Given that there are many problems when trying to use finalization to free resources, we think it’s dangerous to offer it as a solution in SWT, which is designed to expose operating system functionality. We believe it is reasonable to expect programs to explicitly free operating system resources. We see this as simply a fact of life because the current generation of operating systems do not support garbage collection.

When should you free?

You have created an SWT resource, so when do you free it? First of all, for many of the reasons mentioned above, we do not recommend that you use finalization to manage your SWT resources either. If you feel you absolutely must implement a finalize method in your classes, do so only as a "safety net" or "backup", and dispose your resource as usual. Remember to synchronize with the UI thread, as follows:

protected void finalize() { // NOT recommended

super.finalize();

display.asyncExec(new Runnable() {

public void run() {

if (!myResource.isDisposed()) {

myResource.dispose();

}

}

});

}

Even this minimal "safety net" use of finalizers is not recommended, because if memory is limited, your application could end up having to wait for a bunch of your finalize methods to run before the memory from those objects can be reclaimed.

The best way to manage SWT resources is to dispose them as soon as you are through with them. In the case of certain resource types, such as GC and Printer, you almost always create them and dispose them within the scope of the same method.This makes it easy to verify that the resource is being disposed properly, because you only have to go to one place to see the code to create the resource and the code to dispose it. For example:

GC gc = new GC(canvas);

gc.draw... // do some drawing on the canvas using the GC

gc.dispose();

In the case of resources that are set into a GC for drawing, you need to be careful not to dispose them while they are still set into the GC. Dispose the GC before the resource. For example:

Font font = new Font (display, "Courier", 10, SWT.NORMAL);

GC gc = new GC(canvas);

gc.setFont(font);

gc.drawText... // do some text drawing on the canvas using the GC

gc.dispose();

font.dispose();

Remember that you do not dispose the GC that is passed to you in a paint listener, because you did not allocate it!

public void paintControl(PaintEvent event) {

event.gc.draw...

// do NOT call event.gc.dispose();

}

If you are using graphics resources in a widget - for example, widget.setFont(font) - it is often best to clean these up when the widget they are used in is disposed, so you can hook a dispose listener on the widget as follows:

widget.addDisposeListener(new DisposeListener() {

public void widgetDisposed(DisposeEvent e) {

font.dispose();

}

}

If resources are shared by multiple widgets (or other objects) in your application, then you may want to implement a reference-counting scheme in a "resource manager" class, which would keep track of the number of references to a resource, and dispose the resource when the reference count reaches 0. In this case, you would ask your resource manager for each resource you need, perhaps something like this:

Image image = myResourceManager.getNamedResource("imageName");

and the dispose listener on your widget(s) might look something like this:

widget.addDisposeListener(new DisposeListener() {

public void widgetDisposed(DisposeEvent e) {

myResourceManager.dispose(image);

}

});

Whether or not to use a reference-counting scheme, and how to implement it, are design decisions that must be made on a per-application basis.

Widgets themselves do not usually need to be disposed programmatically. A shell and its children are disposed when the user closes its window. There are a couple of places where shells are typically disposed programmatically: when the user selects File->Exit in an application window, or OK in a dialog. Here is an example of disposing a shell when File->Exit is selected:

// Create menu bar

Menu menuBar = new Menu(shell, SWT.BAR);

// Create File menu

MenuItem item = new MenuItem(menuBar, SWT.CASCADE);

item.setText("File");

Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);

item.setMenu(fileMenu);

// Create File -> Exit menu item and add selection listener

item = new MenuItem(fileMenu, SWT.NULL);

item.setText("Exit");

item.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

shell.close(); // calls dispose() - see note below

}

});

Note that we actually call close() instead of dispose(). This gives the application a chance to cancel the close operation if necessary (for example, if something was not saved) inside of a shell listener. We could call dispose(), which would simply destroy the shell without invoking the shell listener, but it's better style to call close().

A note about Color

Color is just like any other SWT resource-based object. To ensure that your application works on all platforms for all display types, always follow rule 1. If you are using a high-color (direct palette) display and you forget to follow the rules for disposing Color, you may notice that nothing bad happens. But if the rules for disposing Color are not followed on a low-color (indexed palette) display, real problems can occur. You should always program your application with the assumption that some of your customers will be using low-color displays. For more information, see the SWT Color Model article.

Conclusion

When you use SWT, you will probably need to manage operating system resources allocated by SWT objects. Resource management takes thought to get right. Getting into good habits and following the rules reduces the programming effort significantly. Simple rules help make it clear who has responsibility for disposing resources:
  • If you created it, you dispose it (if you didn't create it, don't dispose it).
  • Disposing the parent disposes the children.

Further reading

JFace provides a registry mechanism on top of SWT for image and font resource management. See the article Using Images in the Eclipse UI for more information on the JFace image management facilities.

References

[JB] Bloch, Joshua. Effective Java - Programming Language Guide. Addison-Wesley, Boston, 2001.
ISBN: 0-201-31005-8.

[JLS] Gosling James, Bill Joy, Guy Steele, Gilad Bracha. The Java Language Specification, Second Edition
http://java.sun.com/docs/books/jls/second_edition/html/j.title.doc.html

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics