Monday, April 17, 2017

Implications of the Presence of StringBuffer

When I am working on legacy code and run across instances of StringBuffer, I typically replace them with instances of StringBuilder. Although a performance advantage can be gained from this change, I often change it in places I know will have little noticeable effect in terms of performance. I feel it's worth making the change for a variety of reasons in addition to the potential for performance benefit. There's rarely a reason to not choose StringBuilder over StringBuffer (API expectations are the most common exception) and the existence of StringBuffer in code misleads and provides a bad example to those new to Java.

In the book The Pragmatic Programmer: From Journeyman to Master, Andy Hunt and David Thomas discuss "the importance of fixing the small problems in your code, the 'broken windows'." Jeff Atwood touched on this subject in the post The Broken Window Theory and it has been more recently addressed in the posts Software Rot, Entropy and the Broken Window Theory and Don't leave broken windows. The presence of StringBuffer implies a staleness in the code. In effect, use of StringBuffer may not be a "broken window," but it's a really old, leaky single-pane window that should be replaced with a modern, energy-efficient double-pane window.

I found Peter Lawrey's recent blog post StringBuffer, and how hard it is to get rid of legacy code to be an interesting take on other implications of StringBuffer that still exist in code. Lawrey quotes the last paragraph of the StringBuffer class Javadoc documentation, "As of release JDK 5, this class has been supplemented with an equivalent class designed for use by a single thread, StringBuilder. The StringBuilder class should generally be used in preference to this one, as it supports all of the same operations but it is faster, as it performs no synchronization." Lawrey then uses simple Java methods and jmap to demonstrate that instances of StringBuffer are still used in classes and libraries delivered with the JDK even as late as Java 8.

Lawrey points out that the presence of StringBuffer in frequently used Java code more than a decade after the introduction of "drop-in replacement" StringBuilder is evidence of how difficult it is to "clean up legacy code." Lawrey's full conclusion states, "Using StringBuffer on startup doesn’t make much difference, but given it has a well known, drop in replacement, and it is still used, even in new functionality more than ten years later shows how hard it can be to clean up legacy code or to change thinking to get people to use best practice libraries."

I decided to try out one of Lawrey's simplest examples when compiled with Java 8 Update 121 and when compiled with a recent release of OpenJDK 9. I (slightly) adapted Lawrey's example to the simple "Main" class listing shown next.

Main.java

import java.io.IOException;

/**
 * (Slightly) adapted class from blog post
 * "StringBuffer, and how hard it is to get rid of legacy code" at
 * https://vanilla-java.github.io/2017/04/13/String-Buffer-and-how-hard-it-is-to-get-rid-of-legacy-code.html
 */
public class Main
{
   /**
    * Main function that instantiates this Java "application" and does nothing
    * else until "ENTER" is pressed.
    */
   public static void main(final String[] args) throws IOException
   {
      System.out.println("Waiting [press ENTER to exit] ..");
      System.in.read();
   }
}

The following screen snapshot shows the output of using jcmd with its -all option (includes unreachable objects in the inspection) to show the number of instances of StringBuffer and StringBuilder in the simple Java application when compiled and run against three different versions of Java (Java 8 Update 102, Java 8 Update 121, and OpenJDK 9.0 ea+164). The execution of jcmd is performed in PowerShell and so Select-String is used similarly to how grep is used in Linux.

Although the versions of the class compiled and executed with versions of Java 8 had instances of StringBuffer, the version compiled with and executed against Java 9 only had instances of StringBuilder. It looks like the resolution of JDK-8041679 ("Replace uses of StringBuffer with StringBuilder within core library classes") and JDK-8043342 ("Replace uses of StringBuffer with StringBuilder within crypto code") have had their intended effect.

2 comments:

@DustinMarx said...

Error Prone's Bug Pattern called "JdkObsolete" is used to "suggest alternatives to obsolete JDK classes" and includes suggesting alternatives for use of StringBuffer.

@DustinMarx said...

JDK-8229022 ("BufferedReader performance can be improved by using StringBuilder") has been resolved in JDK 14 Early Access Build 17. The bug description states, "BufferedReader uses StringBuffer. Swapping that to use StringBuilder seems to give a ~13% performance boost." The bug report also references How fast can a BufferedReader read lines in Java? for additional background details.