5.17. Miscellaneous Platform Features
The following sections detail important but miscellaneous features of
the Java platform, including properties, preferences, processes, and
management and instrumentation.
5.17.1. Properties
java.util.Properties
is a subclass of java.util.Hashtable, a legacy
collections class that predates the Collections API introduced in
Java 1.2. A Properties object maintains a mapping
between string keys and string values and defines methods that allow
the mappings to be written to and read from a simple text file or (in
Java 5.0) an XML file. This makes the Properties
class ideal for configuration and user preference files. The
Properties class is also used for the system
properties returned by System.getProperty( ):
import java.util.*;
import java.io.*;
// Note: many of these system properties calls throw a security exception if
// called from untrusted code such as applets.
String homedir = System.getProperty("user.home"); // Get a system property
Properties sysprops = System.getProperties(); // Get all system properties
// Print the names of all defined system properties
for(Enumeration e = sysprops.propertyNames(); e.hasMoreElements();)
System.out.println(e.nextElement());
sysprops.list(System.out); // Here's an even easier way to list the properties
// Read properties from a configuration file
Properties options = new Properties(); // Empty properties list
File configfile = new File(homedir, ".config"); // The configuration file
try {
options.load(new FileInputStream(configfile)); // Load props from the file
} catch (IOException e) { /* Handle exception here */ }
// Query a property ("color"), specifying a default ("gray") if undefined
String color = options.getProperty("color", "gray");
// Set a property named "color" to the value "green"
options.setProperty("color", "green");
// Store the contents of the Properties object back into a file
try {
options.store(new FileOutputStream(configfile), // Output stream
"MyApp Config File"); // File header comment text
} catch (IOException e) { /* Handle exception */ }
// In Java 5.0 properties can be written to or read from XML files
try {
options.storeToXML(new FileOutputStream(configfile), // Output stream
"MyApp Config File"); // Comment text
options.loadFromXML(new FileInputStream(configfile)); // Read it back in
}
catch(IOException e) { /* Handle exception */ }
catch(InvalidPropertiesFormatException e) { /* malformed input */ }
5.17.2. Preferences
Java 1.4 introduced the
Preferences API, which is specifically tailored for working with user
and systemwide preferences and is more useful than Properties for
this purpose. The Preferences API is defined by the
java.util.prefs package. The key class in that
package is Preferences. You can obtain a
Preferences object that contains user-specific
preferences with the static method
Preferences.userNodeForPackage() and obtain a
Preferences object that contains systemwide
preferences with
Preferences.systemNodeForPackage(). Both methods
take a java.lang.Class object as their sole
argument and return a Preferences object shared by
all classes in that package. (This means that the preference names
you use must be unique within the package.) Once you have a
Preferences object, use the get(
) method to query the string value of a named preference,
or use other type-specific methods such as getInt(
), getBoolean(), and
getByteArray(). Note that to query preference
values, a default value must be passed for all methods. This default
value is returned if no preference with the specified name has been
registered or if the file or database that holds the preference data
cannot be accessed. A typical use of Preferences
is the following:
package com.davidflanagan.editor;
import java.util.prefs.Preferences;
public class TextEditor {
// Fields to be initialized from preference values
public int width; // Screen width in columns
public String dictionary; // Dictionary name for spell checking
public void initPrefs() {
// Get Preferences objects for user and system preferences for this package
Preferences userprefs = Preferences.userNodeForPackage(TextEditor.class);
Preferences sysprefs = Preferences.systemNodeForPackage(TextEditor.class);
// Look up preference values. Note that you always pass a default value.
width = userprefs.getInt("width", 80);
// Look up a user preference using a system preference as the default
dictionary = userprefs.get("dictionary",
sysprefs.get("dictionary",
"default_dictionary"));
}
}
In addition to the get( ) methods for querying
preference values, there are corresponding put()
methods for setting the values of named preferences:
// User has indicated a new preference, so store it
userprefs.putBoolean("autosave", false);
If your application wants to be notified of user or system preference
changes while the application is in progress, it may register a
PreferenceChangeListener with
addPreferenceChangeListener(). A
Preferences object can export the names and values
of its preferences as an XML file and can read preferences from such
an XML file. (See importPreferences( ),
exportNode(), and exportSubtree(
) in java.util.pref.Preferences in the
reference section.) Preferences objects exist in a
hierarchy that typically corresponds to the hierarchy of package
names. Methods for navigating this hierarchy exist but are not
typically used by ordinary applications.
5.17.3. Processes
Earlier in the chapter, we saw how
easy it is to create and manipulate multiple threads of execution
running within the same Java interpreter. Java also has a
java.lang.Process class that represents an
operating system process running externally to the interpreter. A
Java program can communicate with an external process using streams
in the same way that it might communicate with a server running on
some other computer on the network. Using a
Process is always platform-dependent and is rarely
portable, but it is sometimes a useful thing to do:
// Maximize portability by looking up the name of the command to execute
// in a configuration file.
java.util.Properties config;
String cmd = config.getProperty("sysloadcmd");
if (cmd != null) {
// Execute the command; Process p represents the running command
Process p = Runtime.getRuntime().exec(cmd); // Start the command
InputStream pin = p.getInputStream(); // Read bytes from it
InputStreamReader cin = new InputStreamReader(pin); // Convert them to chars
BufferedReader in = new BufferedReader(cin); // Read lines of chars
String load = in.readLine(); // Get the command output
in.close(); // Close the stream
}
In Java 5.0 the
java.lang.ProcessBuilder class provides a more
flexible way to launch new processes than the
Runtime.exec() method.
ProcessBuilder allows control of environment
variables through a Map and makes it simple to set
the working directory. It also has an option to automatically
redirect the standard error stream of the processes it launches to
the standard output stream, which makes it much easier to read all
output of a Process.
import java.util.Map;
import java.io.*
public class JavaShell {
public static void main(String[] args) {
// We use this to start commands
ProcessBuilder launcher = new ProcessBuilder();
// Our inherited environment vars. We may modify these below
Map<String,String> environment = launcher.environment();
// Our processes will merge error stream with standard output stream
launcher.redirectErrorStream(true);
// Where we read the user's input from
BufferedReader console =
new BufferedReader(new InputStreamReader(System.in));
while(true) {
try {
System.out.print("> "); // display prompt
System.out.flush(); // force it to show
String command = console.readLine(); // Read input
if (command.equals("exit")) return; // Exit command
else if (command.startsWith("cd ")) { // change directory
launcher.directory(new File(command.substring(3)));
}
else if (command.startsWith("set ")) {// set environment var
command = command.substring(4);
int pos = command.indexOf('=');
String name = command.substring(0,pos).trim();
String var = command.substring(pos+1).trim();
environment.put(name, var);
}
else { // Otherwise it is a process to launch
// Break command into individual tokens
String[] words = command.split(" ");
launcher.command(words); // Set the command
Process p = launcher.start(); // And launch a new process
// Now read and display output from the process
// until there is no more output to read
BufferedReader output = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line;
while((line = output.readLine()) != null)
System.out.println(line);
// The process should be done now, but wait to be sure.
p.waitFor();
}
}
catch(Exception e) {
System.out.println(e);
}
}
}
}
5.17.4. Management and Instrumentation
Java 5.0
includes the powerful
JMX API for remote monitoring
and management of running applications. The full
javax.management API is beyond the scope of this
book. The reference section does cover the
java.lang.management package, however: this
package is an application of JMX for the monitoring and management of
the Java virtual machine itself.
java.lang.instrument is another
Java
5.0 package: it allows the definition of
"agents"
that can be used to instrument the running JVM. In VMs that support
it, java.lang.instrument can be used to redefine
class files as they are loaded to add profiling or coverage testing
code, for example. Class redefinition is beyond the scope of this
chapter, but the following code uses the new instrumentation and
management features of Java 5.0 to determine resource usages of a
Java program. The example also demonstrates the
Runtime.addShutdownHook()
method, which registers code to be
run when the VM starts shutting down.
import java.lang.instrument.*;
import java.lang.management.*;
import java.util.List;
import java.io.*;
public class ResourceUsageAgent {
// A Java agent class defines a premain() method to run before main()
public static void premain(final String args, final Instrumentation inst) {
// This agent simply registers a shutdown hook to run when the VM exits
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
// This code runs when the VM exits
try {
// Decide where to send our output
PrintWriter out;
if (args != null && args.length() > 0)
out = new PrintWriter(new FileWriter(args));
else
out = new PrintWriter(System.err);
// Use java.lang.management to query peak thread usage
ThreadMXBean tb = ManagementFactory.getThreadMXBean();
out.printf("Current thread count: %d%n",
tb.getThreadCount());
out.printf("Peak thread count: %d%n",
tb.getPeakThreadCount());
// Use java.lang.management to query peak memory usage
List<MemoryPoolMXBean> pools =
ManagementFactory.getMemoryPoolMXBeans();
for(MemoryPoolMXBean pool: pools) {
MemoryUsage peak = pool.getPeakUsage();
out.printf("Peak %s memory used: %,d%n",
pool.getName(), peak.getUsed());
out.printf("Peak %s memory reserved: %,d%n",
pool.getName(), peak.getCommitted());
}
// Use the Instrumentation object passed to premain()
// to get a list of all classes that have been loaded
Class[] loaded = inst.getAllLoadedClasses();
out.println("Loaded classes:");
for(Class c : loaded) out.println(c.getName());
out.close(); // close and flush the output stream
}
catch(Throwable t) {
// Exceptions in shutdown hooks are ignored so
// we've got to print this out explicitly
System.err.println("Exception in agent: " + t);
}
}
});
}
}
To monitor the resource usage of a Java program with this agent, you
first must compile the class normally. You then store the generated
class files in a JAR file with a manifest that specifies the class
that contains the premain() method. Create a
manifest file that contains this line:
Premain-Class: ResourceUsageAgent
Create the JAR file with a command like this:
% jar cmf manifest agent.jar ResourceUsageAgent*.class
Finally, to use the agent, specify the JAR file and the agent
arguments with the -javaagent flag to the
Java
interpreter:
% java -javaagent:agent.jar=/tmp/usage.info my.java.Program
|