Java theory and practice: Whose object is it, anyway?

Garbage collection reduces the need to track object ownership -- but it doesn't eliminate it

In a language without garbage collection, such as C++, significant attention must be paid to memory management. For each dynamic object, you must either implement reference counting to simulate the effect of garbage collection, or manage the "ownership" of each object -- identifying which class is responsible for deleting an object. Such ownership is usually not maintained declaratively, but rather by (often undocumented) convention. While garbage collection means that Java developers don't have to worry (much) about memory leaks, sometimes we still do have to worry about object ownership to prevent data races and unwanted side effects. In this article, Brian Goetz identifies some of the situations where Java developers must pay attention to object ownership.

Share:

Brian Goetz (brian@quiotix.com), Principal Consultant, Quiotix Corp

Brian Goetz has been a professional software developer for the past 15 years. He is a Principal consultant at Quiotix, a software development and consulting firm in Los Altos, California. See Brian's published and upcoming articles in popular industry publications.



24 June 2003

Also available in Russian Japanese

If you learned to program before 1997, chances are that the first programming language you learned didn't provide transparent garbage collection. Each new operation had to be balanced by a corresponding delete, or else your program would leak memory, and eventually the memory allocator would fail and your program would crash. Whenever you allocated an object with new, you had to ask yourself, who will delete this object and when?

Aliases, also known as ...

A primary contributor to the complexity of memory management is aliasing: having more than one copy of a pointer or reference to the same block of memory or object. Aliases occur naturally all the time. For example, in Listing 1 there are at least four references to the Something object created on the first line of makeSomething:

  • The something reference
  • At least one reference held within collection c1
  • The temporary aSomething reference created when something is passed as an argument to registerSomething
  • At least one reference held within collection c2
Listing 1. Aliases in typical code
    Collection c1, c2;
    
    public void makeSomething {
        Something something = new Something();
        c1.add(something);
        registerSomething(something);
    }

    private void registerSomething(Something aSomething) {
        c2.add(aSomething);
    }

There are two major memory-management hazards to avoid in non-garbage-collected languages: memory leaks and dangling pointers. To prevent memory leaks, you must ensure that each allocated object is eventually deleted. To avoid dangling pointers (the dangerous situation where a block of memory is freed but a pointer still references it), you must delete the object only after the last reference is released. The mental and digital bookkeeping required to satisfy these two constraints can be significant.


Managing object ownership for memory management

Besides garbage collection, two other approaches are commonly used to deal with the problems of aliasing: reference counting and ownership management. Reference counting involves keeping a count of how many references to a given object are extant, then deleting the object automatically after the last one is released. In C -- and in most versions of C++ prior to the mid-1990s -- this was impossible to do automatically. The Standard Template Library (STL) allows for the creation of "smart" pointers than can automatically do reference counting (see the shared_ptr class from the open source Boost library, or the simpler auto_ptr class from the STL for examples).

Ownership management is the process of designating one pointer to be the "owning" pointer and all other aliases to be only temporary second-class copies, and deleting the object only when the owning pointer is released. In some cases, ownership can be "transferred" from one pointer to another, such as a method for writing to a socket that accepts a buffer as an argument and that deletes the buffer when the write completes (such methods are often called sinks). In this case, the ownership of the buffer has been effectively transferred, and the calling code must assume the buffer to have been deleted when the transferee method returns. (Ownership management can be further simplified by ensuring that all alias pointers have scope that is tied to the call stack, such as method parameters or local variables, and by copying the object if a reference is going to be held by a non-stack-scoped variable.)


So what?

At this point, you're probably wondering why I'm even talking about memory management, aliases, and object ownership. After all, garbage collection is one of the core features of the Java language, and memory management is a chore of the past! Just let the garbage collector handle it; that's its job. Those who have been freed from the chore of memory management don't want to go back, and those who never had to deal with it can't even imagine how horrible life must have been as a programmer in the bad old days -- like 1996.


Watch out for dangling aliases

So does that mean we can say goodbye to the concept of object ownership? Yes ... and no. Garbage collection does indeed obviate the need for explicit resource deallocation, most of the time. (I'll discuss some exceptions in a future column.) However, there is one area where ownership management is still very much an issue in Java programs, and that is the problem of dangling aliases. Java developers often rely on implicit assumptions of object ownership to determine which references should be considered read-only (a const pointer, in C++ parlance) and which can be used to modify the referenced object's state. A dangling alias occurs when two classes each think (mistakenly) that they hold the only writeable reference to a given object. When this happens, one or both of the classes will be confused when the object's state changes unexpectedly.

Case in Point

Consider the code in Listing 2, where a UI component holds a Point object to represent its location. When MathUtil.calculateDistance is called to calculate how far the object has moved, we are relying on an implicit and subtle assumption -- that calculateDistance will not mutate the state of the Point objects passed to it, or worse, maintain a reference to the Point objects (such as by storing them in a collection or passing them to another thread) that might then later be used to mutate their state after calculateDistance returns. In the case of calculateDistance, it might seem absurd to worry about such behavior, as this would clearly be an unspeakable breach of etiquette. But by passing a mutable object to another method, it is simply an act of faith that the object will be returned to you unharmed and that there will be no future undocumented side effects on the object's state (such as the method sharing the reference with another thread, which might wait five minutes and then change the object's state).

Listing 2. Passing mutable objects to foreign methods is an act of faith
    private Point initialLocation, currentLocation;

    public Widget(Point initialLocation) {
        this.initialLocation = initialLocation;
        this.currentLocation = initialLocation;
    }

    public double getDistanceMoved() {
        return MathUtil.calculateDistance(initialLocation, currentLocation);
    }
    
    . . . 

    // The ill-behaved utility class MathUtil
    public static double calculateDistance(Point p1, 
                                           Point p2) {
        double distance = Math.sqrt((p2.x - p1.x) ^ 2 
                                    + (p2.y - p1.y) ^ 2);
        p2.x = p1.x;
        p2.y = p1.y;
        return distance;
    }

What a silly example

The obvious and universal response to this example -- namely, that it is a silly example -- merely underscores the fact that the concept of object ownership is alive and well, and merely undocumented, in Java programs. The calculateDistance method shouldn't mutate the state of its arguments because it doesn't "own" them -- the calling method does, of course. So much for not having to think about object ownership.

Here's a more practical example of the confusion that can be caused by not knowing who owns an object. Consider again a UI component that has a Point property to represent its location. Listing 3 shows three ways to implement the accessor methods setLocation and getLocation. The first way is the laziest and offers the highest performance, but it has several vulnerabilities to both deliberate attacks and innocent mistakes.

Listing 3. Value and reference semantics for getters and setters
public class Widget {
    private Point location;

    // Version 1: No copying -- getter and setter implement reference 
    // semantics
    // This approach effectively assumes that we are transferring 
    // ownership of the Point from the caller to the Widget, but this 
    // assumption is rarely explicitly documented. 
    public void setLocation(Point p) {
        this.location = p;
    }

    public Point getLocation() {
        return location;
    }

    // Version 2: Defensive copy on setter, implementing value 
    // semantics for the setter
    // This approach effectively assumes that callers of 
    // getLocation will respect the assumption that the Widget 
    // owns the Point, but this assumption is rarely documented.
    public void setLocation(Point p) {
        this.location = new Point(p.x, p.y);
    }

    public Point getLocation() {
        return location;
    }

    // Version 3: Defensive copy on getter and setter, implementing 
    // true value semantics, at a performance cost
    public void setLocation(Point p) {
        this.location = new Point(p.x, p.y);
    }

    public Point getLocation() {
        return (Point) location.clone();
    }
}

Now, consider this innocent-looking use of setLocation:

    Widget w1, w2;
    . . . 
    Point p = new Point();
    p.x = p.y = 1;
    w1.setLocation(p);
    
    p.x = p.y = 2;
    w2.setLocation(p);

Or this:

    w2.setLocation(w1.getLocation());

Under version 1 of the setLocation/getLocation accessor implementation, it may look like the location of the first Widget will be (1, 1) and the second will be (2, 2), but, in fact, both will be (2, 2). This may well be confusing both to the caller (because the first Widget got moved unexpectedly) and to the Widget class (because its location changed without the Widget code being involved). In the second example, you may think you are moving Widget w2 to where Widget w1 is currently located, but you're actually constraining w2 to follow w1 every time w1 moves.

Defensive copies

Version 2 of setLocation does better: it makes a copy of the argument passed to it to ensure that there are no aliases to the Point that could mutate its state unexpectedly. But it doesn't go far enough either, because the following code will also have the (likely undesired) effect of moving the Widget without its knowledge:

    Point p = w1.getLocation();
    . . .
    p.x = 0;

Version 3 of getLocation and setLocation are fully safe against malicious or careless uses of alias references. This safety comes at some performance cost: the creation of a new object every time a getter or setter is called.

The different versions of getLocation and setLocation have different semantics, often referred to as value semantics (version 3) and reference semantics (version 1). Unfortunately, which semantics the implementer intends is rarely documented. The result is that users of the class don't know, and therefore should assume the worst.

The technique used by version 3 of getLocation and setLocation is called defensive copying, and despite the obvious performance cost, you should get in the habit of using it nearly all the time when returning or storing references to mutable objects or arrays, especially if you are writing a general-purpose facility that may be called by code that you haven't personally written (and often even then). Cases where an aliased mutable object gets unexpectedly modified can crop up in a number of subtle and surprising ways, and debugging them can be extremely difficult.

But it gets worse. Let's say that as a user of the Widget class, you don't know whether the accessors have value or reference semantics. Prudence would dictate that defensive copies are needed when calling them, too. So, if you wanted to move w2 to the current location of w1, you would have to do this:

    Point p = w1.getLocation();
    w2.setLocation(new Point(p.x, p.y));

If Widget implemented its accessors as in version 2 or 3, now we'd be creating two temporary objects for every call -- one outside the call to setLocation and one inside.

Document accessor semantics

The real problem with version 1 of getLocation and setLocation is not that they are vulnerable to confusing alias side effects (which they are), but that their semantics were not properly documented. If accessors were clearly documented to have reference semantics (instead of the commonly assumed value semantics), callers might be more likely to realize that when they call setLocation, they are transferring the ownership of that Point object to another entity, and be less likely to assume they still own it and can reuse it.


Immutability to the rescue

These problems with Point could have easily been solved if Point had been made immutable in the first place. There can be no side effects on immutable objects, and caching a reference to an immutable object is always safe from alias problems. All of the questions about the semantics of the setLocation and getLocation accessors are unambiguously determined if Point is immutable. Accessors for immutable properties will always have value semantics and do not need the defensive copying on either side of the call, making them more efficient.

So why wasn't Point made immutable in the first place? It was probably for performance reasons; early JVMs had less efficient garbage collectors. The object creation overhead of creating a new Point every time an object on the screen moved (even the mouse) probably seemed daunting at the time, and the overhead of making defensive copies probably seemed out of the question.

In hindsight, the decision to make Point mutable turned out to be costly to program clarity and performance. The mutability of the Point class creates a documentation burden for every method that accepts a Point parameter or returns a Point. Namely, does it mutate the Point or retain a reference to it after it returns? Given that so few classes actually include such documentation, the safe strategy when calling a method that doesn't document its call semantics or side-effect behavior is to create a defensive copy before passing it to any such method.

Ironically, the performance benefit of the decision to make Point mutable is dwarfed by the additional cost of the defensive copying required by Point's mutability. In the absence of clear documentation (or a leap of faith), defensive copying is required on both sides of a method call -- by the caller, because it doesn't know if the callee will rudely mutate the Point, and by the callee, if it is retaining a reference to the Point.


A real-world example

Here is another example of a dangling alias problem, which is very similar to one I recently saw in a server application. This application used publish-and-subscribe messaging internally to communicate events and status updates to other agents within the server. Agents could subscribe to whichever message streams interested them. Once published, messages delivered to other agents would probably be processed at a later time in a different thread.

Listing 4 shows a typical messaging event, posting notification of a new high bid in an auction system, and the code that generates it. Unfortunately, the interaction of the messaging event implementation and the caller implementation conspire to create a dangling alias. By simply copying the array reference instead of cloning it, both the message and the class that produces it hold a reference to the master copy of the previous bids array. If there is any delay between the time the message is published and the time it is consumed, subscribers could see different values for the previous5Bids array than was current at the time of publication, and multiple subscribers might see different values for the previous bids from each other. In this case, the subscriber would see a historical value of the current bid, and more up-to-date values of the previous bids, creating the illusion that the previous bids were higher than the current bid. It's not hard to imagine how this would cause problems -- and worse, such a problem would only manifest itself when the application is under heavy load. Making the message classes immutable and cloning mutable references such as arrays at construction time would have prevented this problem.

Listing 4. Dangling array alias in publish-subscribe messaging code
public interface MessagingEvent { ... }

public class CurrentBidEvent implements MessagingEvent { 
  public final int currentBid;
  public final int[] previous5Bids;

  public CurrentBidEvent(int currentBid, int[] previousBids) {
    this.currentBid = currentBid;
    // Danger -- copying array reference instead of values
    this.previous5Bids = previous5Bids;
  }

  ...
}

  // Now, somewhere in the bid-processing code, we create a 
  // CurrentBidEvent and publish it.  
  public void newBid(int newBid) { 
    if (newBid > currentBid) { 
      for (int i=1; i<5; i++) 
        previous5Bids[i] = previous5Bids[i-1];
      previous5Bids[0] = currentBid;
      currentBid = newBid;

      messagingTopic.publish(new CurrentBidEvent(currentBid, previousBids));
    }
  }
}

Guidelines for mutable objects

If you are creating a mutable class M, you should be prepared to write a lot more documentation about the treatment of references to M than if M were immutable. First, you must choose whether methods that accept M as arguments or return an M object will use value or reference semantics, and be prepared to document that clearly in every other class that uses M in its interface. If any methods that accept or return an M object are implicitly assuming that the ownership of M is being transferred, you must document that, too. You must also be prepared to accept the performance overhead of making defensive copies where necessary.

One special case where we are forced to deal with the issue of object ownership is with arrays, as arrays cannot be immutable. There may be a cost to making defensive copies when passing an array reference to another class, but unless you are sure that the other class either makes its own copy or that it will not hold the reference for longer than the duration of the call, you probably want to make a copy before passing the array. Otherwise, you can easily end up in a situation where the classes on both sides of the call implicitly assume that they own that array, with unpredictable results.


Summary

Dealing with mutable classes requires a lot more care than immutable classes. When passing references to mutable objects between methods, you need to clearly document under what cases the object's ownership is being transferred. And in the absence of clear documentation, you must make defensive copies on both sides of a method call. While the justification for mutability is often based on performance, because there is no need to create a new object every time its state changes, the performance cost of defensive copying can easily outweigh the performance savings from reduced object creation.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=86617
ArticleTitle=Java theory and practice: Whose object is it, anyway?
publish-date=06242003