Tuesday, February 19, 2019

Stashing Previously Set psql Variables

The command-line based "PostgreSQL interactive terminal" known as psql is handy for manipulating and accessing data in a PostgreSQL database. Because of its command-line nature, psql is particularly well suited for use in scripts. One of the psql features that makes it even more useful in scripting contexts is its support for "meta-commands". As the psql documentation states, "Anything you enter in psql that begins with an unquoted backslash is a psql meta-command that" and "these commands make psql more useful for administration or scripting."

When writing psql scripts, it is often preferable to set some variables locally for the time period the script is being run, but might also be desirable to not change these variables permanently for the psql session if it's likely that other scripts or other work will be performed from the psql session after the script's conclusion. In this post, I will demonstrate use of psql's \set meta-command to temporarily store off previous settings of variables to restore these settings at the script's conclusion.

The psql documentation describes "a number of ... variables [that] are treated specially by psql." These "specially treated variables" are the ones that we most likely want to ensure that we set for our script's duration only and the restore their previously set values upon script exit. The documentation describes these "specially treated variables": "They represent certain option settings that can be changed at run time by altering the value of the variable, or in some cases represent changeable state of psql. By convention, all specially treated variables' names consist of all upper-case ASCII letters (and possibly digits and underscores). To ensure maximum compatibility in the future, avoid using such variable names for your own purposes." Examples of these "specially treated variables" include AUTOCOMMIT, ECHO, ECHO_HIDDEN, PROMPT1, PROMPT2, PROMPT3, and VERBOSE, but there are many more.

For demonstration purposes, let's suppose you want to set the ECHO variable to something other than its default (none). For our purposes, we'll set ECHO to queries. We want to make sure, however, that we set it back to whatever it was when our script was called before leaving the script. The following simple psql logic accomplishes this.

\set PRIOR_ECHO :ECHO
\set ECHO queries

-- Run various queries for which you want to see the query itself output before the query results ...

\set ECHO :PRIOR_ECHO

It's as simple as that to temporarily set "specially treated variables" for your script's convenience without permanently changing the settings for the caller who might be running your script in the same psql session. The key things to remember are that the \set meta-command is always all lowercase, the specially treated variables have names that are always all uppercase but the specially treated variable values do not need to be uppercase (and typically are not), and the values in a variable can be accessed by prefixing the variable name with a colon (:).

Friday, February 15, 2019

PostgreSQL's psql \set versus SET

It is easy for someone who is new to PostgreSQL and who uses PostgreSQL's terminal editor psql to confuse the commands \set and SET. This post contrasts these commands and provides a brief overview of other commands that include the word "set".

The easiest way to remember how to differentiate \set from SET is to keep in mind that the "backslash commands" such as \set are "meta commands" for the command-line psql tool and do not mean anything to the PostgreSQL database itself. The SET command, which lacks a backslash, is a PostgreSQL database command that happens to be executed against the database from the psql command-line client.

Contrasting \set and SET

Command \set SET or set
(or other case-insensitive variation1)
Context psql terminal editor configuration meta command
(interactive client terminal configuration)
PostgreSQL database configuration command
(server configuration)
Ends with Semicolon? No Yes
Command Case Sensitive? Yes
(must be exactly \set)
No
Parameter/Variable Case Sensitive? Yes2 No
How are Settings Displayed? \set3 SHOW ALL; or show all;4
\echo :variable_name SHOW variable_name;5
Examples \set AUTOCOMMIT on SET search_path TO myschema, public;
Footnotes
  1. I prefer to use all uppercase letters for SET to more clearly differentiate from /set.
  2. Two variables with same letters but different cases are two distinct variables.
  3. \set displays all variables when no arguments are provided to it.
  4. Any case variation (even sHoW aLl;) works.
  5. Any case variation of command and/or variable name also works.

There are two more psql meta commands that that "set" things and include the name "set". The \pset met command configures how psql presents "query result tables." Like \set, \pset can be specified without argument to see all of the current presentation settings.

Unlike the psql meta commands \set and \pset, the \gset psql metacommand does affect the PostgreSQL server because \gset submits the query buffer to the server and then stores the output returned from the server into specified psql variables. I discussed \gset with a few additional details in the blog post "Setting PostgreSQL psql Variable Based Upon Query Result."

Although \set and SET can be used to set variables, the easiest way to distinguish between them is to consider that the backslash commands such as \set are psql commands (and so \pset sets variables in the psql client tool) and commands without the backslash such as SET are PostgreSQL commands sent to the server from psql or from any other client (but ultimately set variables on the server).

Monday, February 4, 2019

jcmd, Circa JDK 11

Nicolas Fränkel recently published a survey of command-line tools delivered with OpenJDK 11 in the blog post "OpenJDK 11, tools of the trade." In that post, he briefly summarizes the tools jps (a JVM process status tool), jinfo (JVM configuration details), jmap (classes/objects on the heap), jstack (thread analysis), and graphical tool JConsole (monitor Java applications).

All of these tools are handy for Java developers to be aware of to apply as needed and Fränkel's post provides a nice introductory overview for those new to these tools. In recent years, I've moved toward applying the single jcmd tool instead of most of the other command-line tools (though it doesn't replace graphical tool JConsole in any way) as I've discussed in the post "jcmd: One JDK Command-Line Tool to Rule Them All."

There is a brief discussion on the related /r/java subreddit thread regarding jcmd versus the individual tools. I can see advantages to both approaches (using jcmd or using multiple individual tools). I contrast my perceptions of their relative advantages and disadvantages here.

jcmd Versus the Rest
jcmdOther Tools
Single interactive tool Different tools with varying names and options
More keystrokes/commands required to run functionality due to interactive nature Fewer keystrokes required for those familiar with commands and options and for cases where command/options being used are supported for the given JVM process
jcmd <pid> help provides the specific functions supported on that JVM process for jcmd analysis Results of running individual tool against JVM process is primary method of detecting that tool's support (or lack thereof) for that process
Supports only most commonly used subset of functionality of some of the individual tools Each tool, by its nature, sets the bar for supported functionality
Newer with fewer online resources Older with more online resources
Not considered "experimental" Several of the individual tools (jps, jinfo, jmap, jstack, and more) are labeled "experimental" and are subject to change/removal (Tools Reference states that "experimental tools are unsupported and should be used with that understanding. They may not be available in future JDK versions. Some of these tools aren’t currently available on Windows platforms.")
Significant jcmd provided-details are available programmatically via DiagnosticCommandMBean Direct corresponding programmatic access is rarely available for individual tools

Whether to use jcmd or one of the individual tools largely comes down to individual taste and preferences. Those who are already experienced with existing individual tools may prefer the more direct approach of those tools while those not familiar with the individual tools may prefer the interactive ability provided by jcmd for determining what tools and options are available. I certainly prefer non-experimental tools over "experimental" tools, but many of these tools have been labeled "experimental" for many versions of the JDK and are still with us.

The previously mentioned blog post "jcmd: One JDK Command-Line Tool to Rule Them All" describes how to use jcmd's interactive features to identify its capabilities supported for various JVM processes. There is a table toward the end of that post that "maps" jcmd options to some of the corresponding individual tools' commands and options. I reproduce that here for convenience.

FunctionalityjcmdSimilar Tool
Listing Java Processes jcmd jps -lm
Heap Dumps jcmd <pid> GC.heap_dump jmap -dump <pid>
Heap Usage Histogram jcmd <pid> GC.class_histogram jmap -histo <pid>
Thread Dump jcmd <pid> Thread.print jstack <pid>
List System Properties jcmd <pid> VM.system_properties jinfo -sysprops <pid>
List VM Flags jcmd <pid> VM.flags jinfo -flags <pid>

The jcmd tool continues to be enhanced. JDK 9 saw several enhancements to jcmd via JEP 228 ("Add More Diagnostic Commands"). In JDK 11, support for displaying classloader hierarchies was added to jcmd. Here is a simple screen snapshot of that support for classloaders hierarchies in action.

As Fränkel concludes in his post, "The JDK offers a lot of out-of-box tools to help developers" and "they are a huge asset in a developer’s day-to-day job." This sentiment applies whether one chooses to use the individual JDK-provided tools or chooses to use jcmd.

Saturday, February 2, 2019

Revealing the Queries Behind psql's Backslash Commands

PostgreSQL's psql interactive terminal tool provides several useful "backslash list commands" such as \d (lists "relations" such as tables, views, indexes, and sequences), \dt (lists tables), \di (lists indexes), \ds (lists sequences), \dv (lists views), \df (lists functions), \du (lists roles), and \? (displays help/usage details on backslash commands). These commands are concise and much simpler to use than writing the queries against PostgreSQL system catalogs (pg_class, pg_roles, pg_namespace, pg_trigger, pg_index, etc.) and information_schema that would provide the same types of details.

Although the psql backslash commands are easier to use than their associated queries, there are situations when it is important to know the full query behind a particular command. These situations include needing to perform a slightly different/adapted query from that associated with the pre-built command and needing to perform similar queries in scripts or code that are being used as PostgreSQL clients instead of psql. These situations make it important to be able to determine what queries psql is performing and the psql option -E (or --echo-hidden) allow that.

The PostgreSQL psql documentation states that the psql options -E and --echo-hidden "echo the actual queries generated by \d and other backslash commands." The documentation adds commentary on why this is useful, "You can use this to study psql's internal operations." When psql is started with the -E or --echo-hidden options, it will display the query associated with a backslash command before executing that command. The next screen snapshot illustrates this for the \du command used to show roles.

From use of psql -E and execution of the command \du, we're able to see that the query underlying \du is this:

Although the query is not nearly as nice to use as \du, we are now able to adapt this query for a related but different use case and are able to run this query from a PostgreSQL client other than psql.

Thursday, January 31, 2019

JDK 9/JEP 280: String Concatenations Will Never Be the Same

JEP 280 ("Indify String Concatenation") was implemented in conjunction with JDK 9 and, according to its "Summary" section, "Change[s] the static String-concatenation bytecode sequence generated by javac to use invokedynamic calls to JDK library functions." The impact this has on string concatenation in Java is most easily seen by looking at the javap output of classes using string concatenation that are compiled in pre-JDK 9 and post-JDK 9 JDKs.

The following simple Java class named "HelloWorldStringConcat" will be used for the first demonstration.

Contrasting of the differences in -verbose output from javap for the HelloWorldStringConcat class's main(String) method when compiled with JDK 8 (AdoptOpenJDK) and JDK 11 (Oracle OpenJDK) is shown next. I have highlighted some key differences.

JDK 8 javap Output

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcat.class
  Last modified Jan 28, 2019; size 625 bytes
  MD5 checksum 3e270bafc795b47dbc2d42a41c8956af
  Compiled from "HelloWorldStringConcat.java"
public class dustin.examples.HelloWorldStringConcat
  minor version: 0
  major version: 52
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

JDK 11 javap Output

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcat.class
  Last modified Jan 28, 2019; size 908 bytes
  MD5 checksum 0e20fe09f6967ba96124abca10d3e36d
  Compiled from "HelloWorldStringConcat.java"
public class dustin.examples.HelloWorldStringConcat
  minor version: 0
  major version: 55
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: iconst_0
         5: aaload
         6: invokedynamic #3,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
        11: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: return

The "Description" section of JEP 280 describes this difference: "The idea is to replace the entire StringBuilder append dance with a simple invokedynamic call to java.lang.invoke.StringConcatFactory, that will accept the values in the need of concatenation." This same section shows a similar comparison of compiled output for a similar string concatenation example.

The compiled output from JDK 11 for the simple string concatenation is not just fewer lines than its JDK 8 counterpart; it also has fewer "expensive" operations. Potential performance improvement can be gained from not needing to wrap primitive types, and not needing to instantiate a bunch of extra objects. One of the primary motivations for this change was to "lay the groundwork for building optimized String concatenation handlers, implementable without the need to change the Java-to-bytecode compiler" and to "enable future optimizations of String concatenation without requiring further changes to the bytecode emitted by javac."

JEP 280 Does Not Affect StringBuilder or StringBuffer

There's an interesting implication of this in terms of using StringBuffer (which I have a difficult time finding a good use for anyway) and StringBuilder. It was a stated "Non-Goal" of JEP 280 to not "introduce any new String and/or StringBuilder APIs that might help to build better translation strategies." Related to this, for simple string concatenations like that shown in the code example at the beginning of this post, explicit use of StringBuilder and StringBuffer will actually preclude the ability for the compiler to make use of the JEP 280-introduced feature discussed in this post.

The next two code listings show similar implementations to the simple application shown above, but these use StringBuilder and StringBuffer respectively instead of string concatenation. When javap -verbose is executed against these classes after they are compiled with JDK 8 and with JDK 11, there are no significant differences in the main(String[]) methods.

Explicit StringBuilder Use in JDK 8 and JDK 11 Are The Same

JDK 8 javap Output for HelloWorldStringBuilder.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuilder.class
  Last modified Jan 28, 2019; size 627 bytes
  MD5 checksum e7acc3bf0ff5220ba5142aed7a34070f
  Compiled from "HelloWorldStringBuilder.java"
public class dustin.examples.HelloWorldStringBuilder
  minor version: 0
  major version: 52
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

JDK 11 javap Output for HelloWorldStringBuilder.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuilder.class
  Last modified Jan 28, 2019; size 627 bytes
  MD5 checksum d04ee3735ce98eb6237885fac86620b4
  Compiled from "HelloWorldStringBuilder.java"
public class dustin.examples.HelloWorldStringBuilder
  minor version: 0
  major version: 55
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

Explicit StringBuffer Use in JDK 8 and JDK 11 Are The Same

JDK 8 javap Output for HelloWorldStringBuffer.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuffer.class
  Last modified Jan 28, 2019; size 623 bytes
  MD5 checksum fdfb90497db6a3494289f2866b9a3a8b
  Compiled from "HelloWorldStringBuffer.java"
public class dustin.examples.HelloWorldStringBuffer
  minor version: 0
  major version: 52
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuffer
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuffer."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        21: invokevirtual #7                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

JDK 11 javap Output for HelloWorldStringBuffer.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringBuffer.class
  Last modified Jan 28, 2019; size 623 bytes
  MD5 checksum e4a83b6bb799fd5478a65bc43e9af437
  Compiled from "HelloWorldStringBuffer.java"
public class dustin.examples.HelloWorldStringBuffer
  minor version: 0
  major version: 55
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuffer
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuffer."":()V
        10: ldc           #5                  // String Hello,
        12: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        15: aload_0
        16: iconst_0
        17: aaload
        18: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        21: invokevirtual #7                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
        24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        27: return

JDK 8 and JDK 11 Handling of Looped String Concatenation

For my last example of JEP 280 changes in action, I use a code sample that may offend some Java developers' sensibilities and perform a string concatenation within a loop. Keep in mind this is just an illustrative example and all will be okay, but don't try this at home.

JDK 8 javap Output for HelloWorldStringConcatComplex.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcatComplex.class
  Last modified Jan 30, 2019; size 766 bytes
  MD5 checksum 772c4a283c812d49451b5b756aef55f1
  Compiled from "HelloWorldStringConcatComplex.java"
public class dustin.examples.HelloWorldStringConcatComplex
  minor version: 0
  major version: 52
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // String Hello
         2: astore_1
         3: iconst_0
         4: istore_2
         5: iload_2
         6: bipush        25
         8: if_icmpge     36
        11: new           #3                  // class java/lang/StringBuilder
        14: dup
        15: invokespecial #4                  // Method java/lang/StringBuilder."":()V
        18: aload_1
        19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        22: iload_2
        23: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        26: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        29: astore_1
        30: iinc          2, 1
        33: goto          5
        36: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        39: aload_1
        40: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        43: return

JDK 11 javap Output for HelloWorldStringConcatComplex.main(String[])

Classfile /C:/java/examples/helloWorld/classes/dustin/examples/HelloWorldStringConcatComplex.class
  Last modified Jan 30, 2019; size 1018 bytes
  MD5 checksum 967fef3e7625965ef060a831edb2a874
  Compiled from "HelloWorldStringConcatComplex.java"
public class dustin.examples.HelloWorldStringConcatComplex
  minor version: 0
  major version: 55
     . . .
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // String Hello
         2: astore_1
         3: iconst_0
         4: istore_2
         5: iload_2
         6: bipush        25
         8: if_icmpge     25
        11: aload_1
        12: iload_2
        13: invokedynamic #3,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
        18: astore_1
        19: iinc          2, 1
        22: goto          5
        25: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        28: aload_1
        29: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        32: return

In the presentation "Enough java.lang.String to Hang Ourselves ...," Dr. Heinz M. Kabutz and Dmitry Vyazelenko discuss the JEP 280-introduced changes to Java string concatenation and summarize it succinctly, "+ is no longer compiled to StringBuilder." In their "Lessons from Today" slide, they state, "Use + instead of StringBuilder where possible" and "recompile classes for Java 9+."

The changes implemented in JDK 9 for JEP 280 "will enable future optimizations of String concatenation without requiring further changes to the bytecode emitted by javac." Interestingly, it was recently announced that JEP 348 ("Java Compiler Intrinsics for JDK APIs") is now a Candidate JEP and it aims to use a similar approach for compiling methods String::format and Objects::hash.

Monday, January 28, 2019

Custom Compact Number Patterns with JDK 12 Compact Number Formatting

The post "Compact Number Formatting Comes to JDK 12" has been the subject of discussion on a java subreddit thread. Concerns expressed in that thread related to presentation of the compact number formatting deal with digits of precision displayed and the compact number patterns displayed. The digits of precision can be addressed via use of CompactNumberFormat.setMinimumFractionDigits(int) and this approach is discussed in more detail in the post "Using Minimum Fractional Digits with JDK 12 Compact Number Formatting." The second issue (dislike for the compact patterns used in the pre-built CompactNumberFormat instances for certain languages) is addressed in this post.

As far as I can determine (and I certainly could be missing something), there's no method on CompactNumberFormat that allows for the compact number patterns to be set on an existing instance of CompactNumberFormat. However, if the constructor for CompactNumberFormat is used to obtain an instance of this class (rather than using one of the overloaded static factory methods on NumberFormat), then the compact number patterns can be supplied via that constructor to the new instance of CompactNumberFormat. This is demonstrated in the next code listing (also available on GitHub).

/**
 * Provides an instance of {@code CompactNumberFormat} that has been
 * custom created via that class's constructor and represents an
 * alternate Germany German representation to that provided by an
 * instance of {@code CompactNumberFormat} obtained via the static
 * factory methods of {@code NumberFormat} for {@code Locale.GERMANY}.
 *
 * @return Instance of {@code CompactNumberFormat} with customized
 *    alternate German compact pattern representations.
 */
private static CompactNumberFormat generateCustomizedGermanCompactNumberFormat()
{
   final String[] germanyGermanCompactPatterns
      = {"", "", "", "0k", "00k", "000k", "0m", "00m", "000m", "0b", "00b", "000b", "0t", "00t", "000t"};
   final DecimalFormat germanyGermanDecimalFormat
      = acquireDecimalFormat(Locale.GERMANY);
   final CompactNumberFormat customGermanCompactNumberFormat
      = new CompactNumberFormat(
         germanyGermanDecimalFormat.toPattern(),
         germanyGermanDecimalFormat.getDecimalFormatSymbols(),
         germanyGermanCompactPatterns);
   return customGermanCompactNumberFormat;
}

There are three items worth special emphasis in the above code listing:

  1. The CompactNumberFormat(String, DecimalFormatSymbols, String[]) constructor allows for an array of Strings to be passed to the instance to specify the compact number patterns.
  2. The nature of the String[] defining compact number patterns is defined in the Javadoc for CompactNumberFormat class, which states, "The compact number patterns are represented in a series of patterns where each pattern is used to format a range of numbers."
    • That same Javadoc provides a US locale based example that provides these values for the range 100 to 1014 and it was that example that I adapted here.
    • More or fewer than 15 patterns can be supplied, but the first supplied pattern always corresponds to 100. The Javadoc states, "There can be any number of patterns and they are strictly index based starting from the range 100."
    • For demonstration purposes here, I adapted the patterns from an observation on the subreddit thread referenced earlier. I don't know much about German, but the argument for SI-based suffixes made a lot of sense and this is merely for illustrative purposes anyway.
  3. In this example, I retrieved the other two arguments to the CompactNumberFormat constructor from a JDK-provided instance of DecimalFormat for Germany German locale (Locale.GERMANY). This ensured that the decimal pattern and decimal format symbols of my custom Germany German instance of CompactNumberFormat would be the same as those associated with the JDK-provided instance.

The code listing above showed a call to a method called acquireDecimalFormat(Locale) to get a JDK-provided instance of DecimalFormat for Locale.GERMANY. For completeness, that method is shown next.

/**
 * Provides an instance of {@code DecimalFormat} associated with
 * the provided instance of {@code Locale}.
 *
 * @param locale Locale for which an instance of {@code DecimalFormat}
 *    is desired.
 * @return Instance of {@code DecimalFormat} corresponding to the
 *    provided {@code Locale}.
 * @throws ClassCastException Thrown if I'm unable to acquire a
 *    {@code DecimalFormat} instance from the static factory method
 *    on class {@code NumberFormat} (the approach recommended in the
 *    class-level Javadoc for {@code DecimalFormat}).
 */
private static DecimalFormat acquireDecimalFormat(final Locale locale)
{
   final NumberFormat generalGermanyGermanFormat
      = NumberFormat.getInstance(locale);
   if (generalGermanyGermanFormat instanceof DecimalFormat)
   {
      return (DecimalFormat) generalGermanyGermanFormat;
   }
   throw new ClassCastException(
        "Unable to acquire DecimalFormat in recommended manner;"
      + " presented with NumberFormat type of '"
      + generalGermanyGermanFormat.getClass().getSimpleName()
      + "' instead.");
}

The code snippets shown above demonstrate how the compact number patterns can be customized for a given instance of CompactNumberFormat when the compact number patterns associated in instances of that class for a given Locale are not desirable. It'd be nice if there was a method on the CompactNumberFormat class to allow for overriding of some or all of the compact number patterns associated with an existing instance obtained via NumberFormat static factory class, but JDK 12 has entered rampdown phase 2.

JDK 13: What AggressiveOpts?

The Java VM flag -XX:+AggressiveOpts was deprecated in JDK 11 [see JDK-8199777 and JDK-8199778] "because its behavior is ill-defined." The "Problem" section of JDK-8199778 further explains (I added the emphasis):

AggressiveOpts has been used as a catch-all method of enabling various experimental performance features, mostly targeted to improve score on very specific benchmarks. Most things it affected has been removed or integrated over time, leaving the behavior of the flag ill-defined and prone to cause more issues than it'll solve. The only effect that the flag currently has is setting AutoBoxCacheMax = 20000 and BiasedLockingStartupDelay = 500. Both can be done manually by setting the corresponding flags on the command line.

According to the document "Java HotSpot VM Options," the -XX:+AggressiveOpts flag was added with J2SE 5 Update 6 to "turn on point performance compiler optimizations that are expected to be default in upcoming releases."

The article "Java's -XX:+AggressiveOpts: Can it slow you down?" examines the -XX:+AggressiveOpts VM flag in detail and looks at some benchmark comparisons. The article concludes, "By retaining legacy flags you make it less likely to get the benefits of newer, faster features in released JVMs."

A much older Kirk Pepperdine article "Poorly chosen Java HotSpot Garbage Collection Flags and how to fix them!" specifically calls out -XX:+AggressiveOpts as an example of a VM flag whose behavior is unknown. Pepperdine writes that recommendations for use of this flag have not changed since Java SE 5.

When the -XX:+AggressiveOpts flag is passed to the JDK 11 Java launcher, a warning is presented: "VM warning: Option AggressiveOpts was deprecated in version 11.0 and will likely be removed in a future release."

In JDK 12, -XX:+AggressiveOpts has been removed as advertised (JDK-8150552) and a warning was presented to anyone trying to use it in conjunction with the Java launcher. The next screen snapshot displays this warning message that states, "VM warning: Ignoring option AggressiveOpts; support was removed in 12.0" (from JDK 12 Early Access Build #29 [2019/1/24]).

In JDK 13 Early Access builds, the VM won't start if -XX:+AggressiveOpts is specified. This is shown in the next screen snapshot (JDK 13 Early Access Build #5 [2019/1/24]).

As the previous image shows, the VM fails to start in JDK 13 when the -XX:+AggressiveOpts flag is specified and it reports the error message, "Unrecognized VM option 'AggressiveOpts'."

The -XX:+AggressiveOpts flag was deprecated in JDK 11, is removed but only shows a warning when specified in JDK 12, and is removed and prevents the VM from starting when specified in JDK 13.