Code Sample: Create a “Hello World” Program Using the Low Level Persistence Library (LLPL) for Java*

ID 657778
Updated 1/2/2019
Version Latest
Public

author-image

By

File(s): GitHub*
License: BSD 3-clause
Optimized for...  
Operating System: Linux* kernel version 4.3 or higher
Hardware: Emulated: See Emulated: See How to Emulate Persistent Memory Using Dynamic Random-access Memory (DRAM)
Software:
(Programming Language, tool, IDE, Framework)
LLPL for Java*, Persistent Memory Development Kit (PMDK) libraries
Prerequisites: Familiarity with Java*

Introduction

Create a "Hello World" program with key methods in the Low Level Persistence Library (LLPL) for Java*. Learn how to use persistent memory blocks, including heap allocation, Flushable.class, heap setRoot, heap copyFromArray, and heap copyToArray methods. The sample writes a "Hello..." message to persistent memory or reads it back with a console notification, depending on user input. You can try this code sample using Intel® Optane™ DC persistent memory, or with persistent memory emulation using DRAM.

Prerequisites

The article assumes that you have a basic understanding of persistent memory concepts and are familiar with the features of the PMDK. If not, visit the Intel® Developer Zone’s Persistent Memory site, where you’ll find the information you need to get started.

Code Sample Design

The sample creates a persistent memory heap (or pool in PMDK terminology) where it stores the "Hello…" message. The write_hello_string method stores the "Hello…" message to the heap and the read_hello_string method reads the message back from the heap. See the code snippet below for more details. Depending on the option passed in at execution time ("R" for read or "W" for write), the code sample performs a read from or a write to the persistent memory heap. Option "Q" is used to exit the program.

/* This is a simple Java program.
 * FileName : "Hello_llpl.java". 
 */
 
import lib.llpl.*;
import java.util.Scanner;

class Hello_llpl
{
	
	/****************************
	 * This method writes the "Hello..." string to persistent memory.
	 *****************************/
	public static void write_hello_string (byte[] input, Heap h, int size)	
	{
		// block allocation (transactional allocation)
		MemoryBlock newBlock = h.allocateMemoryBlock(size, true);
		
		//Attached the newBlock to the root address
		h.setRoot(newBlock.handle());

		// Write byte array (input) to newBlock @ offset 0 (on both) for 26 bytes
		newBlock.copyFromArray(input, 0, 0, size);

		//Ensure that the array (input) is in persistent memory
		newBlock.flush();

		//Convert byte array (input) to String format and write to console
		System.out.printf("\nWrite the (%s) string to persistent memory.\n",new String(input));
	}

	/****************************
	 * This method reads the "Hello..." string from persistent memory.
	 *****************************/
	public static void read_hello_string(String msg, Heap h, int size)
	{
		// Allocate buffer for string
		// To retrieve byte array from persistent heap
		byte[] output = new byte[size];
		
		//Get the root block address
		long rootAddr = h.getRoot();
		if (rootAddr == 0) {
			System.out.println("Root Block NOT found!");
			System.exit(0);
		} 
		// Map the newBlock to the root
		MemoryBlock newBlock = h.memoryBlockFromAddress(rootAddr);
		
		// Read 26 bytes @ offset 0 from newBlock to byte array (output)
		newBlock.copyToArray(0L, output, 0, size);

		//Convert byte array (output) to String format and write to console
		System.out.printf("\nRead the (%s) string from persistent memory.\n",new String(output));
	}
	
	
	// Your program begins with a call to main().
	// Prints "Hello Persistent Memory" to the console.
	public static void main (String[] args) {
		String option = "0"; 
		byte[] input;
		int size;  // String length
		
		// Define Heap
		Heap h = Heap.getHeap("./persistent_heap", 2147483648L);		
		
		//Initialize the msg string
		String msg = "Hello Persistent Memory!!!";
		
		// Convert String to byte array format
		// To store in persistent heap
		input = msg.getBytes();
		
		// Get the array size
		size = input.length;
		
		//Create a scanner to read the command-line input
		Scanner scanner = new Scanner(System.in);
		
		while (!(option.equals("W") || option.equals("R")
				|| option.equals("Q"))) {
			//Prompt for user's input
		 	System.out.print("\nEnter <W> for Write to persistent memory, " + "<R> for Read from persistent memory, or <Q> to Quit: ");
			
			//Read the input as a String
			option = scanner.next();
		}
		
		switch(option)
		{	case "W":
				write_hello_string(input, h, size);
				break;
			case "R":
				read_hello_string(msg,h, size);
				break;
			case "Q":
				//Quit the program
				break;
			default:
				System.out.printf("\nUnknown option (%s) string.\n", option);
				System.out.printf("\nOptions are  or .\n");
				break;
		}	// End of Switch option
	} 	// End of main function
}	// End of Hello class

Data Structures

The heap object is the primary data structure for the "Hello" program. It represents the persistent memory available to the program which, underneath, is a memory mapped file residing on a persistent memory-aware file system.

a high-level overview of the persistent memory heap used to hold the “Hello...” string

Figure 1. A high-level overview of the persistent memory heap used to hold the "Hello..." string

A Code Walk-Through

Starting in the main program, two variables are defined: a "Heap h" and a "String msg." To get an instance of a heap object, a call is made to the getHeap() method with the location of the file to be memory mapped. If the file does not exist, it is created. The string msg is initialized to "Hello Persistent Memory!!!" and also converted to a byte array. This array will be needed later to write the string to persistent memory.

// Define Heap
Heap h = Heap.getHeap("./persistent_heap", 2147483648L);		
		
//Initialize the msg string
String msg = "Hello Persistent Memory!!!";

// Convert String to byte array format
// To store in persistent heap
input = msg.getBytes();

Next, the program parses command line input to determine whether to read from or write to persistent memory.

The Write Option

When starting the program, the user is prompted to select one of the options. When directed by option "W" to write to persistent memory, the sample uses multiple methods to carry out the task. See the code snippet below for more details:

Step 1: Allocate Persistent Memory

// block allocation (transaction allocation)
MemoryBlock newBlock = h.allocateMemoryBlock(size, true);

The h.allocateMemoryBlock method allocates persistent memory. It requires two parameters:

  1. Size – the number of blocks to allocate
  2. Boolean value - true means that the allocation should be done transactionally

Step 2: Get the root object

//Attached the newBlock to the root address
h.setRoot(newBlock.handle());

The h.setRoot method sets the root address for the root block in heap h.

Step 3: Write the "Hello…" message to persistent memory

// Write byte array (input) to newBlock @ offset 0 (on both) for 26 bytes
newBlock.copyFromArray(input, 0, 0, size);

The h.copyFromArray copies the provided byte array to persistent memory. It requires four parameters:

  1. Source – the input array
  2. Source offset – byte offset from the beginning of the source
  3. Destination offset – byte offset from the beginning of the destination block
  4. Size – the number of bytes to be copied

Step 4: Flush the data in cache to persistent memory

//Ensure that the array (input) is in persistent memory
newBlock.flush();

In this step the newBlock.flush method ensures that the input array flushes any CPU cache lines holding any part of the modified block.

Step 5: Output to console, confirming a successful write

//Convert byte array (input) to String format and write to console
System.out.printf("\nWrite the (%s) string to persistent memory.\n",new String(input));

The Read Option

The "R" option reads the persistent memory. See the code snippet below for more details:

Step 1: Allocate a new byte array to read the data from persistent memory

// Allocate buffer for string
// To retrieve byte array from persistent heap
byte[] output = new byte[size];

Step 2: Get the address for the root block

//Get the root block address
long rootAddr = h.getRoot();

The h.getRoot() method returns the address from the root block, which can be used to recreate the MemoryBlock reference object.

Step 3: Recreate reference object newBlock from a persistent memory address

// Map the newBlock to the root
MemoryBlock newBlock = h.memoryBlockFromHandle(rootAddr);

To access the persistent memory block, we need a point of reference. It will be an object of class MemoryBlock<>, which is recreated from a persistent memory address using the h.memoryBlockFromHandle() method. No new persistent memory is allocated in this call.

Step 4: Read the "Hello…" message

// Read 26 bytes @ offset 0 from newBlock to byte array (output)
newBlock.copyToArray(0L, output, 0, size);

The newBlock.copyToArray method copies a byte array to persistent memory. It requires four parameters:

  1. Source offset – byte offset from the beginning of the source
  2. Destination – new byte output array
  3. Destination offset – byte offset from the beginning of the destination block
  4. Size – the number of bytes to be copied

Step 5: Output to console, confirming a successful read

//Convert byte array (output) to String format and write to console
System.out.printf("\nRead the (%s) string from persistent memory.\n",new String(output));

Compile and Run

A Makefile is included to help you compile and build the binary.

Running ‘make’ compiles the application in the current working directory:

$make

See below for an example of the run after building the binary:

$ make run
java  -cp .:/home/thai/github/llpl/target/classes -Djava.library.path=/home/thai/github/llpl/target/cppbuild Hello_llpl

Enter <W> for Write to persistent memory, <R> for Read from persistent memory, or <Q> to Quit: W

Write the (Hello Persistent Memory!!!) string to persistent memory.
$ make run
java  -cp .:/home/thai/github/llpl/target/classes -Djava.library.path=/home/thai/github/llpl/target/cppbuild Hello_llpl

Enter <W> for Write to persistent memory, <R> for Read from persistent memory, or <Q> to Quit: R

Read the (Hello Persistent Memory!!!) string from persistent memory.
$ make run
java  -cp .:/home/thai/github/llpl/target/classes -Djava.library.path=/home/thai/github/llpl/target/cppbuild Hello_llpl

Enter <W> for Write to persistent memory, <R>​​​​​​​ for Read from persistent memory, or <Q>​​​​​​​ to Quit: Q
$

Summary

The "Hello…" sample code described in this article demonstrates the usage of the several LLPL methods, including allocating persistent memory blocks, flushing the data to persistent memory, and copying to and from persistent memory using LLPL. Build and run the code sample included with this article and find more persistent memory programming examples in the PMDK examples folder in our GitHub* repository.

To learn more about LLPL read Introducing the Low Level Persistent Library (LLPL) for Java*.

About the Author

Thai Le is the software engineer focusing on cloud computing and performance computing analysis at Intel Corporation.

Steven Briscoe is an Industry Technical Specialist driving the adoption of Persistent Memory with Cloud Service Providers at Intel Corporation.

References