JDK-8210050 : Started Java Process with alternate JRE uses file 'lib/modules' in both JREs
  • Type: Bug
  • Component: tools
  • Sub-Component: jlink
  • Affected Version: 10.0.2
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2018-08-27
  • Updated: 2018-08-28
  • Resolved: 2018-08-28
Related Reports
Duplicate :  
Description
ADDITIONAL SYSTEM INFORMATION :
Windows 7 64-Bit, JRE 10.0.2 (10.0.1)

A DESCRIPTION OF THE PROBLEM :
A Java main program (running with JRE-A e.g. 10.0.1, located at a/java) is starting a new Java process with an alternate JRE-B (e.g. 10.0.2 located at b/java) and terminates itself. The new process has still a handle to the modules file from JRE-A. Strangely, it has handles to both modules file in a/java/lib and b/java/lib.

REGRESSION : Last worked in version 8u181

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
(1) create dir c:\temp\test\    
(2) copy JRE 10.0.1  to  c:\temp\test\a\java\    
	directories in c:\temp\test\:
		c:\temp\test\a\java\release
		c:\temp\test\a\java\README.html
		c:\temp\test\a\java\bin\*
		...
(3) copy JRE 10.0.2  to  c:\temp\test\b\java\    
	additional directories in c:\temp\test\:
		c:\temp\test\b\java\release
		c:\temp\test\b\java\README.html
		c:\temp\test\b\java\bin\*
		...
(4) start cmd terminal window and cd to c:\temp\test
(5) set environment to JRE-A
	> set JAVA_HOME=c:\temp\test\a\java
	> set JRE_HOME=%JAVA_HOME%
	> set PATH=%JAVA_HOME%\bin\;%PATH%
(6) copy TestRestartWithNewJre.class to c:\temp\test
(7) run the test program    with parameter  -newjre C:\temp\test\b\java    
	> java -cp . TestRestartWithNewJre -newjre C:\temp\test\b\java
(8) check the result:
	(a) check p1.log: it shows the PID, the environment and properties of the starting process (JRE-A)
	(b) check the task-manager: the starting process (with JRE-A) does not exist anymore
	(c) check p2.log: it shows the PID, the environment and properties of the started process (JRE-B)
		There is also an AccessDeniedException. This is thrown, because the renaming of the JRE-A home dir (a\java -> a\java-<timestramp>) failed.
	(d) check the task-manager: the started process (with JRE-B) is still in process-list (sleeps for 1h)
	(e) The Process-Explorer (procexp.exe, from Windows SysInternals) shows, that the started Java process (JRE-B) has handles to both modules file (from JRE-A and JRE-B)


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The started process (using JRE-B) shouldn't have any reference to files in JRE-A
ACTUAL -
The Process-Explorer (procexp.exe, from Windows SysInternals) shows, that 
(a) the first process (using JRE-A) does not exist anymore
(b) the started Java process (JRE-B) has handles to both modules file (from JRE-A and JRE-B)

---------- BEGIN SOURCE ----------
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TestRestartWithNewJre {

	private static String logFile = "p1.log";
	private static String oldJreHome = null;
	private static String newJreHome = null;
	
	public static void main(String[] args) {
		TestRestartWithNewJre t = new TestRestartWithNewJre();

		try {
			//get params
			setParams(args);
			
			//delete logfiles
			new File( logFile ).delete();
			
			//log params and all system properties
			log("args: " + Arrays.asList(args));
			dumpMap("properties: ", (Map) System.getProperties() );
			dumpMap("env: ", System.getenv() );
			
			// restart with alternate JRE or rename the old one 
			t.start();
		} catch (IOException e) {
			log("IOException found", e);
			log("plese check first which file is locked.....  (process sleep for 1h)");
			
			try {
				Thread.sleep(1000 * 60 * 60);
			} catch (InterruptedException e1) {
				log("sleep interrupted", e1);
			}
		} catch (Exception e) {
			log("error", e);
		}
	}

	
	private void start() throws IOException {
		
		// if new jre home is given, restart this job with the new JRE and put the reference to the old JRE as parameter "-oldjre" (for renaming later on)
		if( newJreHome != null ) {
			File newJreHomeDir = new File(newJreHome);
			if( newJreHomeDir.exists() && newJreHomeDir.isDirectory() ) {
				List<String> cmdItems = new ArrayList<String>();
				String javaExecutable = getJavaExe(newJreHomeDir);
				cmdItems.add(javaExecutable);
				cmdItems.add("-classpath");
				String jarName = getJarName();
				cmdItems.add(jarName);
				cmdItems.add(this.getClass().getName());
				cmdItems.add("-oldjre");
				String jreHome = System.getProperty("java.home");
				cmdItems.add(jreHome);
				cmdItems.add("-logfile");
				cmdItems.add("p2.log");
				
				invokeNewProcess(newJreHome, cmdItems);
				System.exit(1);
			} else {
				log("new JRE home '" + newJreHome + "' does not exist");
				System.exit(1);
			}
			
		} else if( oldJreHome != null ) {
			log("sleep for 2 seconds first, before trying to rename old/unused JRE-home");
			try {
				Thread.sleep(1000 * 2);
			} catch (InterruptedException e1) {
				log("sleep interrupted", e1);
			}
			
			// if old/unused jre home is given, try to rename it
			File oldJreHomeDir = new File(oldJreHome);
			if( oldJreHomeDir.exists() && oldJreHomeDir.isDirectory() ) {
				File targetPath = new File(oldJreHomeDir.getParentFile(), oldJreHomeDir.getName() + "-" + System.currentTimeMillis());
				
				log("try to move '" + oldJreHome + "' to '" + targetPath.getAbsolutePath() + "'");
				
				Path movedTargetPath = Files.move(oldJreHomeDir.toPath(), targetPath.toPath(), StandardCopyOption.ATOMIC_MOVE);
				if( movedTargetPath.toFile().exists() ) {
					log("rename '" + oldJreHome + "' to '" + movedTargetPath.toString() + "' done with success");
				} else {
					log("rename '" + oldJreHome + "' to '" + movedTargetPath.toString() + "' failed");
				}
			} else {
				log("wrong parameter -oldjre '" + oldJreHome + "', path does not exist");
				System.exit(1);
			}
		}
		
		
	}


	/*
	 * get params
	 */
	private static void setParams(String[] args) {
		for( int i=0; i<args.length; i++ ) {
			switch( args[i] ) {
			case "-oldjre":
				if( i+1 < args.length ) {
					oldJreHome = args[++i];
				}
				break;
			case "-newjre":
				if( i+1 < args.length ) {
					newJreHome = args[++i];
				}
				break;
			case "-logfile":
				if( i+1 < args.length ) {
					logFile = args[++i];
				}
				break;
			}
		}
	}
	
	
	/*
	 * get the name of this jar (for restart with alternate jre)
	 */
	private String getJarName() {
		return this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
	}

	/*
	 * determine the path/name of the java executable for the given JRE home (the alternate jre for restart !)
	 */
	private String getJavaExe(final File javaHomeDir) throws IOException {
		final Path start = javaHomeDir.toPath();
		final int maxDepth = 5;
		final StringBuilder sb = new StringBuilder("");
		try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) -> ((String.valueOf(path).endsWith("java.exe")||String.valueOf(path).endsWith("java"))&&path.toFile().isFile()&&(path.toFile().canExecute())))) {
			sb.append( stream.sorted().map(String::valueOf).collect(Collectors.joining(";")) );
		}
		String javaExe[] = sb.toString().split(";");
		if( javaExe.length != 1 ) {
			throw new RuntimeException("java executable not found: " + Arrays.asList(javaExe));
		}
		File javaExeFile = new File(javaExe[0]);
		boolean javaExecutableFound = javaExeFile != null && javaExeFile.exists() && javaExeFile.isFile() && javaExeFile.canRead() && javaExeFile.canExecute();
		if( !javaExecutableFound ) {
			throw new RuntimeException("java file not executable: " + javaExeFile.getAbsolutePath());
		}
		log("getJavaExe: " + javaExeFile.getCanonicalPath() );
		return javaExeFile.getCanonicalPath();
	}

	/*
	 * invoke the new process (with alternate JRE)
	 */
	private void invokeNewProcess(String newJavaHome, List<String> cmdItems) throws IOException {
		log("try to start process: " + cmdItems);
		final ProcessBuilder pb = new ProcessBuilder(cmdItems);
		Map<String,String> pbEnv = pb.environment();
		if( newJavaHome != null ) {
			pbEnv.clear();
			pbEnv.put("JRE_HOME", newJavaHome);
			pbEnv.put("JAVA_HOME", newJavaHome);
		}
		dumpMap("env for new process: ", pbEnv);
		
		pb.redirectErrorStream(true);
		pb.redirectInput();
	    final Process process = pb.start();			
		log("process is started: " + process.isAlive());
	}
	
	/*
	 * log the current map  (env or system properties)
	 */
	private static void dumpMap(final String label, final Map<String,String> map) {
		Set<String> keys = map.keySet();
		java.util.List<String> sortedKeys = new ArrayList<String>(keys);
		Collections.sort(sortedKeys);
		StringBuilder buf = new StringBuilder(label).append(System.lineSeparator());
		for (Iterator<String> iter = sortedKeys.iterator(); iter.hasNext();) {
			String key = iter.next();
			String value = map.get(key);
			buf.append("\t" + key + "=" + value).append(System.lineSeparator());
		}
		log(buf.toString());
	}
	
	/*
	 * log msg
	 */
	private static void log(String msg) {
		log(msg, null);
	}
	
	/*
	 * log msg and exception (if not null)
	 */
	private static void log(String msg, Exception ex ) {
		final DateFormat df = new SimpleDateFormat("yyyyMMdd HH:mm:ss SSS ");
		String pid = " PID: " + getPID() + ", ";
		try {
			
			Files.write(Paths.get(logFile), (df.format(new Date()) + pid + msg + System.lineSeparator()).getBytes("UTF-8"),StandardOpenOption.CREATE,StandardOpenOption.APPEND);
			if( ex != null ) {
				ByteArrayOutputStream bo = new ByteArrayOutputStream();
				PrintStream ps = new PrintStream(bo);
				ex.printStackTrace(ps);
				Files.write(Paths.get(logFile), (df.format(new Date()) + pid + bo.toString() + System.lineSeparator()).getBytes("UTF-8"),StandardOpenOption.CREATE,StandardOpenOption.APPEND);
			}			
		} catch (Exception e) {
			//ignore
		}
	}
	
	/*
	 * determin the current PID
	 */
	private static long getPID() {
		try {
		    String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
		    return Long.parseLong(processName.split("@")[0]);
		} catch( Exception ex ) {
			return -999;
		}
	  }	
}

---------- END SOURCE ----------

FREQUENCY : always



Comments
Received confirmation from submitter that this is not reproducible in JDK 11.
28-08-2018

This was resolved in JDK 11 via JDK-8194734.
28-08-2018

To submitter: I followed the steps outlined in the bug report and was able to reproduce the issue when the JDK in the a and b folders are JDK 10.0.1 and JDK 10.0.2. When I replaced the JDK in both the folders with JDK 11-ea , the process explorer no longer shows Jre2 having a handle to lib/modules folder of Jre1 . Can you please test the reported issue with JDK 11 and let me know if you still experience the issue ?
28-08-2018