Skip to main content

Dispelling Java programming language myths

Peter Haggar (haggar@us.ibm.com)IBM, Software Group
Photo: Peter Haggar
Peter Haggar is a Senior Software Engineer with IBM in Research Triangle Park, North Carolina, and the author of the best-selling book Practical Java Programming Language Guide published by Addison-Wesley. He has a broad range of programming experience, having worked on development tools, class libraries, and operating systems. At IBM, Peter works on emerging Java technology and is currently the specification lead for the Real-Time Specification for Java. Peter is a frequent technical speaker on Java technology at numerous industry conferences. He has worked for IBM for more than 14 years and received a B.S. in Computer Science from Clarkson University. You can contact him at haggar@us.ibm.com.

Summary:  This technical article was originally published a previous issue of the IBM DeveloperToolbox Technical Magazine. Subscribe to the IBM developerWorks journal for the latest technical articles on open standards-based technologies that are available to you offline in a printed publication.

Date:  01 Oct 2001
Level:  Introductory
Activity:  733 views

The Java programming language has matured, and there are many programmers proficient with it. Unfortunately, there remain many programmers that make the wrong choices and assumptions about how the Java platform operates. This article lists several myths that programmers continue to embrace. It reveals each myth, offers an explanation on why the myth is perpetuated, discusses why it is wrong, and explores the facts.

Myth 1: Garbage collection solves all memory-related problems

Garbage collection is a big help to programmers. Not having to worry as much about memory issues is a big advantage for using the Java programming language. However, the fact that the garbage collector cleans up unused memory is not a reason to avoid thinking about memory issues. There are areas that, if ignored, cause trouble.

First, garbage collection algorithms vary from virtual machine (VM) to VM. Therefore, you cannot depend on particular garbage collection behavior across different VM systems. Garbage collection is an active area of research; better, faster, and more accurate collectors are implemented all the time.

Most modern garbage collectors, however, share a number of things in common. One is that when they run, they do not always free all objects that are available for collection. Analysis has shown that most objects in Java programs are short lived. Therefore, the collector, in an effort to be more efficient, examines long-lived objects less frequently than short-lived objects. The thinking is that because most objects are short lived, an object that has been referenced for a longer time will probably be referenced later. Consequently, there is no need to spend time on each garbage collection invocation checking to see if the object is available to be reclaimed.

It may take multiple invocations of the garbage collector to free a particular object's memory. You can suggest that the garbage collector run by calling the System.gc method. Invocation of this method usually results in the garbage collector doing a full collection. Normally, this is a more exhaustive and complete process than when the collector is invoked by the VM itself. The rationale for this is when the VM invokes the collector, it should finish as soon as possible. If a programmer explicitly calls System.gc, the inference is that there is more time available to do the most amount of work. In either case, do not assume that all available objects are collected. There is a better chance that all available objects are collected when the garbage collector is invoked due to an explicit call to System.gc, but there is no guarantee.

Another area where programmers can get into trouble is when they hold on to object references that are not needed. This impedes the garbage collector's ability to free object memory. This situation can occur when you manage your own list.

Consider the ObjStack class in Listing 1. This class has push and pop methods to manage the stack of objects. Both methods utilize an index that is used to indicate the next available slot in the stack. The push method stores a reference to the new object and increments the index, while the pop method decrements the index and returns the top element from the stack.


class ObjStack 
{ 
 private Object[] stack;
 private int index; 
 public void push(Object o) 
 { 
  stack[index] = o; 
  index++; 
 }
 public Object pop()
 {
  index-; 
  return stack[index]; 
 } 
 //... 
} 

Now, create an object of this class with a ten-element stack, and call the push method eight times to add objects, A through H. The stack looks like this:

Now, consider what happens when you call the pop method three times to retrieve the top three elements off the stack. The stack now looks like this:

Note that the stack has not changed other than the index field has been decremented. References to objects F, G, and H have been returned from the pop method as expected. However, the stack still holds references to them. If the code calling the pop method dereferences F, G, and H, the calling code has every expectation that these objects are available for garbage collection, assuming there are no other references to them in the application. However, the stack class used to store these objects has retained a reference to each of them. These objects are never reclaimed unless another object is pushed onto the stack in its place. The correct implementation of the pop method for the stack class is as follows:

public Object pop()
{
 index-;
 Object o = stack[index];
 stack[index] = null;
 return o;
}

In this version of the pop method, when references are returned, the stack dereferences them and thus removes extraneous references to the popped objects that could impede garbage collection in the future. Using this new version of the pop method with the code from Listing 1 results in a stack that looks like this:

Your code should not hold onto references that are no longer needed. The amount of memory that is in use can adversely affect the performance of the application. The less memory available often requires the garbage collector to run more often, hampering performance.


Myth 2: Parameters are passed by reference

The pass-by-value versus pass-by-reference argument is a frequently debated topic in various Java newsgroups. The central point of confusion lies in the following two facts of the Java programming language:

  • Objects are passed by reference
  • Parameters are passed by value

Can these two statements be true at the same time? In a word: Yes. You never pass objects in the Java programming language; you pass references. Therefore, in one sense, the Java programming language is pass by reference. However, when you pass a parameter, it uses only one parameter-passing mechanism, and that is pass by value.

Normally, when programmers talk about pass by value or pass by reference, they refer to the parameter-passing mechanism of a language. The C++ language supports both mechanisms, and former C++ programmers initially seem to be the most unsure about how Java programming passes parameters. Java programming only supports the pass-by-value parameter-passing mechanism and, once understood, makes things simpler.

Variables in Java programming can be one of two types: reference types or primitive types. Both types are handled the same way when passed as arguments to a method. Both are passed by value; neither is passed by reference. This is an important distinction as the subsequent code examples illustrate.

Before going further, it is important to define the terms pass by value and pass by reference. The term pass by value means that when an argument is passed to a function, the function receives a copy of the original value. Therefore, if the function modifies the parameter, only the copy is changed and the original value remains unchanged.

The term pass by reference means that when an argument is passed to a function, the function receives the memory address of the original value, not a copy of the value. Therefore, if the function modifies the parameter, the original value in the calling code is changed.

Some of the confusion about parameter passing in Java programming originates from the fact that many programmers came to Java programming from C++. The C++ language contains both non-reference types and reference types and passes them by value and by reference, respectively. The Java programming language has primitive types and object references; and, therefore, it is logical to think that Java programming uses pass by value for the primitive types and pass by reference for references, much like C++. After all, you might think if you are passing a reference, it must be pass by reference. It is tempting to believe this, but it is not true.

In both C++ and Java programming, when a parameter to a function is not a reference, you pass a copy of the value (pass by value). The difference is with references. In C++, when a parameter to a function is a reference, you pass the reference or the memory address (pass by reference). In Java programming, when an object reference is a parameter to a method, you pass a copy of the reference (pass by value), not the reference itself. This is an important distinction. Note that Java programming does nothing differently when passing parameters of varying types like C++ does. Java programming passes all parameters by value, thus making copies of all parameters regardless of type.

Still not convinced? If Java programming passed parameters by reference, the swap methods in Listing 2 would swap their arguments. Because it passes all parameters by value, these swap methods do not work as intended.


class Swap
{
 public static void main(String args[]) 
 { 
  Integer a, b; 
  int i,j;
  a = new Integer(10);
  b = new Integer(50); 
  i = 5;
  j = 9; 
  System.out.println("Before Swap, a is " + a); 
  System.out.println("Before Swap, b is " + b); 
  swap(a, b); 
  System.out.println("After Swap a is " + a); 
  System.out.println("After Swap b is " + b); 
  System.out.println("Before Swap i is " + i); 
  System.out.println("Before Swap j is " + j); 
  swap2(i,j); 
  System.out.println("After Swap i is " + i);
  System.out.println("After Swap j is " + j); 
 }
 public static void swap(Integer ia, Integer ib)
 {
  Integer temp = ia; 
  ia = ib; 
  ib = temp; 
 }
 public static void swap2(int li, int lj) 
{ 
 int temp = li; 
 li = lj; 
 lj = temp; 
 } 
} 

The output of the code in Listing 2 is:

Before Swap, a is 10 
Before Swap, b is 50
After Swap a is 10 
After Swap b is 50 
Before Swap i is 5 
Before Swap j is 9 
After Swap i is 5 
After Swap j is 9 

Figures 1 through 4 show exactly what happens in the code in Listing 2 with regard to the references. Figure 1 shows the state of the object references and the heap before calling the swap method. Figure 2 shows the object state after calling the swap method but before it executes. Figure 3 shows the object state after the swap method executes but before it returns. Figure 4 shows the object state after the swap method returns.


Figure 1. Before calling method swap
Figure 1

Figure 2. After calling method swap but before it executes
Figure 2

Figure 3. After method swap executes but before it returns
Figure 3

Figure 4. After method swap returns
Figure 4

Because the swap method received a copy of the reference parameters (pass by value), changes to them are not reflected in the calling code. Remember, Java programming passes object references by value.


Myth 3: Atomic operations are thread safe

It has often been written and said that atomic operations in the Java programming language are thread safe. Atomic operations are, by definition, operations that occur without interruption leading many to think they are therefore thread safe. There are, in fact, a number of atomic operations in Java programming, but they are not necessarily thread safe.

This myth came about initially due to efforts to limit the use of synchronization in code. Synchronization has a performance penalty, although that penalty varies from VM to VM. In addition, with the more modern VMs, the efficiency of synchronization is improving. Despite the improvements in synchronization performance, it still has a cost; and programmers forever try to improve the efficiency of their code. Therefore, this myth has perpetuated.

In Java programming, assignments of 32 bits or less are atomic. On 32-bit hardware, all primitive types, except double and long, are typically represented by 32 bits, with double and long being typically represented by 64 bits. In addition, object references, which are implemented as native pointers, are typically 32 bits as well. Operations with these 32-bit types are atomic.

These primitive values are typically represented by 32 or 64 bits. This represents another small myth about Java programming. It is often written and said that the size of primitive types is guaranteed by the language. This is not true. What is guaranteed by the language is the range of values for each primitive type, not its storage size inside of the VM. Therefore, an int will always represent the same range of values. However, one VM implementation might use 32 bits to represent an int, while another uses 64 bits. The only guarantees are that the range of values it represents is the same across all platforms, and operations on 32 bit or smaller values are atomic.

So, how can atomic operations not be thread safe? The main point is that they may indeed be thread safe, but there is no guarantee that they are. Java threads are allowed to keep private copies of variables separate from main memory. This is done as a performance optimization, allowing a thread to work with a private local copy instead of with main memory each time. Consider the following class:


class RealTimeClock 
{ 
 private int clkID; 
 public int clockID() 
 { 
  return clkID; 
 } 
 public void setClockID(int id) 
 { 
  clkID = id;
 } 
//... 
} 

Now, consider one instance of the RealTimeClock class and two threads calling the setClockID and clockID methods concurrently and the following sequence of events:

  1. T1 calls setClockID passing 5
  2. 5 placed in T1's private working memory
  3. T2 calls setClockID passing 10
  4. 10 placed in T2's private working memory
  5. T1 calls clockID, which returns 5
  6. 5 returned from T1's private working memory

The call to clockID should return the value 10, because that is what it was set to by T2. However, 5 is returned, because the read and the write were done to the private working memory and never to main memory. At no point was the variable reconciled with main memory. The assignments are certainly atomic; but because of the allowance for this type of behavior in a VM, they are not necessarily thread safe. There is no guarantee that this problem will occur, but there is no guarantee that it won't occur either. Figure 5 shows how this problem occurs.


Figure 5. Two threads utilizing private working memory
Figure 5

These two threads have private local copies of the variables that are not reconciled with the main memory in the heap. When this implementation is present, the private local variables are reconciled with main memory only under two specific conditions:

  • The variable is declared volatile
  • The variable is accessed within a synchronized method or block

If the variable is declared volatile, it is reconciled with main memory on each access. This reconciliation is guaranteed by the language to be atomic, even for 64-bit values. (Note that many VMs may not implement the volatile keyboard correctly. You can find more information about this on the JavaSoft Web site at www.javasoft.com.) In addition, if the variable is accessed in a synchronized method or block, it is reconciled when the lock is obtained, at method or block entry, and when the lock is released, at method or block exit.

Using either solution guarantees that the call to ClockID returns 10, the correct value. The solution you choose can have performance implications depending on how often the variable is accessed. If concurrency is important and you are not updating many variables, consider using volatile. If you are updating many variables, however, using volatile might be slower than using synchronization. Remember that when variables are declared volatile, they are reconciled with main memory on every access. By contrast, when synchronized is used, the variables are reconciled with main memory only when the lock is obtained and when the lock is released. However, synchronization makes the code less concurrent.

Consider using synchronized if you are updating many variables and do not want the cost of reconciling each of them with main memory on every access, or you want to eliminate concurrency for another reason.


Myth 4: Synchronized code is the same as a critical section

Synchronization is often referred to as a critical section. A critical section is one in which only one thread can execute at a time. It is possible to have synchronized code execute in multiple threads concurrently.

The confusion occurs because some programmers think that synchronization locks the code it surrounds. It doesn't. Synchronization locks objects, not code. Therefore, if you have a class with a synchronized method, this method can be executed concurrently by two different threads if each thread creates its own object of that class. The code in Listing 3 demonstrates the problem.


class Foo extends Thread 
{ 
  private int val; 
  public Foo(int v) 
  { 
   val = v; 
  } 
  public synchronized void printVal(int v) 
  { 
   while(true) 
    System.out.println(v); 
  } 
  public void run() 
  { 
   printVal(val); 
  } 
 }
class SyncTest 
{ 
 public static void main(String args[]) 
 { 
  Foo f1 = new Foo(1); 
  f1.start(); 
  Foo f2 = new Foo(3);
  f2.start(); 
 } 
} 

Running the SyncTest code produces interleaved output between 1 and 3. If the printVal method were a critical section, you would see only 1 or 3 output, not both. Because both are displayed, it proves that both threads are executing the printVal method concurrently even though it is synchronized and never leaves the method due to the infinite loop.

To implement a true critical section whereby one, and only one, thread can ever execute the code at a time, you must synchronize on a global object or on the class literal. The code in Listing 3 is changed to synchronize on the class literal, thus ensuring that only one thread will ever execute the synchronized method at a given time. This is shown in Listing 4.


class Foo extends Thread 
{ 
 private int val; 
 public Foo(int v) 
 { 
  val = v; 
 } 
 public void printVal(int v) 
 { 
  synchronized(Foo.class) { 
   while(true)
    System.out.println(v); 
  } 
 } 
 public void run() 
 { 
  printVal(val); 
 } 
} 

Instead of synchronizing on the individual object instance, the Foo Class is changed to synchronize on the class literal. Because there is only one class literal for class Foo, both threads are synchronizing on the same lock; therefore, only one thread will ever execute the printVal method at a time.

This code also could be fixed by synchronizing on a common object. For example, consider if class Foo had a static object reference. Both methods could synchronize on this object and achieve thread safety.


Myth 5: Waiting threads are awakened in priority order

When writing multithreaded code, often the situation arises where you have more than one thread waiting on an event. This occurs when more than one thread calls wait inside a synchronized method or block that is locked on the same object. Waiting threads are awakened when another thread calls notify or notifyAll from within a synchronized method or block that is locked on the same object. The notify call wakes up only one thread. Therefore, if more than one thread is waiting, there will be no contention for the lock. The notifyAll call, on the other hand, wakes up all waiting threads to compete for the lock. However, only one thread will get the lock; the others will block.

When multiple threads are waiting, the question is which thread runs after a call to notify or notifyAll? Many programmers incorrectly assume that there is a predefined order to how threads are awakened. Some think that the highest priority thread is awakened first, while others might think it is the thread that has been waiting the longest. Unfortunately, neither assumption is true. In these situations, the thread that is awakened is undefined. It might be the thread with the highest priority or the thread that has been waiting the longest, but there is no guarantee that it will be.

The priority of a thread does not determine whether it is notified (in the case of using the notify method) or in what order multiple threads are notified (in the case of using the notifyAll method). Therefore, you should never make assumptions about the order in which threads are awakened as a result of calls to these methods. In addition, you should never make assumptions about the scheduling of a thread during preemption. Thread scheduling is implementation-dependent and varies by platform. It is unwise to make this type of assumption if your code is to be portable.

In addition, the notifyAll method, like the notify method, does not provide a way to specify the order in which waiting threads are notified. The order depends on the Java virtual machine, and no guarantees are made beyond the fact that all waiting threads are awakened. This behavior presents a problem when you need to notify multiple threads in a particular order.

There are two ways to achieve a controlled order of awakened threads:

  • Use the specific notification pattern
  • Use a VM implemented with the Real-Time Specification for Java (RTSJ)

Specific notification pattern

This pattern, developed by Tom Cargill, specifies how you can control the order of threads to be awakened from a call to notify and notifyAll. This is accomplished through a separate lock object for each thread, or set of threads, that needs to be notified together. This enables notification to happen in a defined order to a specific lock object and thus to its associated thread or threads.

The execution overhead of this pattern is minimal if implemented properly. There is, however, some added code complexity. This added complexity can be displaced with the additional control this pattern gives you. You should consider implementing this pattern if you have the need to control the notification order of multiple threads.


RTSJ

The RTSJ changes the standard behavior of certain Java semantics. One of these semantics is ensuring that waiting threads are queued in priority order. Therefore, when one or more threads are waiting and a call is made to notify or notifyAll, the thread with the highest priority executes first. The others must wait.

Generally, it is not recommended to use a real-time implementation for anything but real-time programs. There are various trade-offs that are made to enable the Java programming language for real-time programming. One overriding principle in the creation of the RTSJ is that timeliness takes precedence over execution speed.


Running the samples with VisualAge for Java

The following steps show how the sample code can be run with the VisualAge for Java, Professional Edition, Version 4.0 product:

  1. Copy the code into a directory of your choice.
  2. Open the workbench into an existing workspace.
  3. Add a project called JavaMyths.
  4. From the JavaMyths project, import the directory where you stored the code from this article.
  5. Open the JavaMyths project to view, update, compile, and run the code.

Conclusion

Conventional wisdom is not always accurate. Code written with assumptions about any of these myths can result in inefficient code or code that is simply broken. In addition, some of the myths only apply to certain virtual machine implementations. For example, coding without knowledge of Myth 3 can result in code that behaves properly on one or more VM implementations, but incorrectly on others. This type of problem is extremely difficult to debug. The same type of situation can exist when coding to Myth 5. On several executions of your code, the highest priority thread might be the one that is awakened after a call to notify or notifyAll. However, this might not be the behavior on every execution of the code or of the same code run on different VM implementations.


Resources

  • Categories: Java tools, VisualAge

  • VisualAge for Java

  • IBM Developer Kit for Windows

About the author

Photo: Peter Haggar

Peter Haggar is a Senior Software Engineer with IBM in Research Triangle Park, North Carolina, and the author of the best-selling book Practical Java Programming Language Guide published by Addison-Wesley. He has a broad range of programming experience, having worked on development tools, class libraries, and operating systems. At IBM, Peter works on emerging Java technology and is currently the specification lead for the Real-Time Specification for Java. Peter is a frequent technical speaker on Java technology at numerous industry conferences. He has worked for IBM for more than 14 years and received a B.S. in Computer Science from Clarkson University. You can contact him at haggar@us.ibm.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Sample IT projects
ArticleID=10178
ArticleTitle=Dispelling Java programming language myths
publish-date=10012001
author1-email=haggar@us.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).