New DDR Extensions and ValueTypes

This blog is intended to serve as a technical overview of current Direct Dump Reader (DDR) implementation and an instruction to add new DDR extensions. It is the responsibility of Eclipse OpenJ9 developers to modify DDR whenever there are some changes in J9VM. Additionally, I briefly introduce memory layouts of ValueTypes in J9VM and a new DDR command flatobject which is used to analyze ValueTypes. Please remember this: the spec of ValueTypes is not finalized because it’s a part of Project Valhalla and this project is still in the experimental stage.

What is DDR?

DDR is the Direct Dump Reader. It is a diagnostic component that reads OpenJ9 state from core files, or running processes, and provides an interface to that state for diagnostic tooling. It can provide an implementation of the DTFJ (Diagnostic Tooling Framework for Java) interface that doesn’t rely on jextract being run on system dumps. Nowadays, DDR is incorporated as a part of OpenJ9 and developers don’t need to install it separately. Even if you want to disable DDR, you can add --disable-ddr option to build without DDR when you run configuration.

How to run DDR in Docker and how to use it?

Since we do most of our work in a Docker container, it is essential to know how to enable DDR in a Docker container and how to generate the core dump file you want to investigate. Unfortunately, Docker containers disable DDR by default. Thanks to lovely developer Babneet, we have a method to set it in a Docker container. Here is a link to the instructions:

Although this blog talks about debugging with GDB, we still have to configure our Docker containers using the above method if we want to use DDR.

The next step is to generate the core dump file we want. Although there are lots of ways to do that, like gcore in gdb, I feel that the most convenient method is to use dump agents. Here is a link to an overview of dump agents:

To leverage dump agents, we simply add –Xdump option to our Java execution command when we run OpenJ9 functional tests. Since we are talking about its relationship with ValueTypes, I will focus on introducing how to run ValueType functional tests. There are some instructions in the file, but in addition to those instructions, we have to add some dependencies before running it:

apt-get install libtext-csv-perl
apt-get install libxml-parser-perl
apt-get install ant
apt-get ant-contrib

Then you can run DDR with your test results.

What is the memory layout of ValueTypes in J9VM?

Since there are already some blogs and presentations about valuetypes, I will assume that readers have a basic understanding of it. I will explain what are valuetypes briefly here. Object identity has footprint and performance costs, but these costs are most burdensome for small objects, which do not have many fields to amortize the extra costs. ValueTypes are objects to represent pure data aggregates by removing these unnecessary features. For example, LockWord is unneeded for ValueTypes, because ValueTypes are immutable, developers don’t care whether they are changed. Because of that, there is no LockWord field in the data representing a ValueType. Because of that, ValueTypes cannot be synchronized.

I will demonstrate by using some code and figures here. If we have a ValueType called FlattenedLine2D:

value class Point2D {
  int x;
  int y

value class FlattenedLine2D {
  flattened Point2D st;
  flattened Point2D en;

If it is not a ValueType, it will have three two pointers as its fields:

That means there are 36 bytes used in total because FlattenedLine2D object is 12 bytes and each point2D object is 12 bytes. If it is a flattened ValueType, it will save much more memory, like this:

It only occupies 20 bytes in the heap memory. This is a significant improvement.

Why flatobject command is needed in DDR?

Without new flatobject command, DDR will ignore flattened fields and get fields’ content and offset incorrectly.

Given a value type Line2D without any flattened fields:

value Line2D {
    Point2D st;
    Point2D en;

Inspecting a Line2D instance in today’s DDR would look something like:

> !j9object 0x7055bbaf0
!J9Object 0x00000007055BBAF0 {
      struct J9Class* clazz = !j9class 0x9F2500   // Line2D
      Object flags = 0x00000000;
      LPoint2D; st = !fj9object 0x7ffef39f6 (offset=0) (Line2D)
      LPoint2D; en = !fj9object 0x7ffef39f6 (offset=4) (Line2D)

The !fj9object denotes that the field is stored as a compressed reference. In the example above stand en contain the same value. Running !fj9object 0x7ffef39f6 would output:

> !fj9object 0x7ffef39f6
!J9Object 0x00000007FFEF39F8 {
      struct J9Class* clazz = !j9class 0x9F0E00   // Point2D
      Object flags = 0x00000000;
      I x = 0xffeeffee (offset=0) (Point2D)
      I y = 0xaabbaabb (offset=4) (Point2D)

With Q-types the JVM is allowed to flatten the value in place, similar to a nested C struct. In DDR flattened q-types currently look like this:

> !j9object 0x7fff56a58
!J9Object 0x00000007FFF56A58 {
      struct J9Class* clazz = !j9class 0x9F1D00   // FlattenedLine2D
      Object flags = 0x00000000;
      QPoint2D; st = !fj9object 0xffeeffee (offset=0) (FlattenedLine2D)
      QPoint2D; en = !fj9object 0xaabbaabb (offset=4) (FlattenedLine2D)

Instead of showing the address of st and en it shows the contents of st.

The memory layout of this object is:

0x7fff56a58: 009f1d00 // J9Class/flags
0x7fff56a5C: ffeeffee  // st.x
0x7fff56a60: aabbaabb // st.y
0x7fff56a64: ccddccdd // en.x
0x7fff56a68: aaffaaff  //en.y

The examples above show what DDR currently outputs.

Flatobject command will consider flattened fields.

The usage of it is that :!flatobject [addressOfContainer],[[fieldNum] … ]
AddressOfContainer is the address of the class/value that contains the flattened valueType field. In the case of multi-level field flattening, addressOfContainer is the address of the top-most container. In other words, addressOfContainer must always point at something with a header.

Here is flatobject output for the same object:

> !j9object 0x7fff56a58
!J9Object 0x00000007FFF56A58 {
      struct J9Class* clazz = !j9class 0x9F1D00   // FlattenedLine2D
      Object flags = 0x00000000;
      QPoint2D; st = !flattenedj9object 0x7fff56a58,0 (offset=0) (Point2D)
      QPoint2D; en = !flattenedj9object 0x7fff56a58,1 (offset=8) (Point2D)

This gives you an overview of the current Direct Dump Reader (DDR) implementation and guidance on adding new DDR extensions. For questions and comments, please join the conversation in the OpenJ9 Slack workspace.

1 Reply to “New DDR Extensions and ValueTypes”

Leave a Reply