如何使用Java编程获取所有安装的Java虚拟机(非默认虚拟机)?

8

有没有人知道如何使用Java编程方式获取已安装的所有JVM(不包括默认的)?

例如,用户计算机上安装了2个JVM:

JDK 5
JDK 6

我需要知道所有已安装的版本,以便切换正在使用的版本(默认情况下),然后调用javac程序来使用特定的JDK版本编译一些源代码。
我在网上找了一些信息,发现:
- 如何通过编程方式获取Java版本(而不是默认版本)? - 如何通过编程方式获取jdk / javac的路径? 但我没有找到我需要的内容。

4
什么操作系统,因为这将取决于操作系统。 - user177800
10个回答

17

我最近处理了一个非常相似的情况。以下代码几乎完全符合您的需求。它寻找Java JRE和JDK,不仅限于JDK,但应该很容易根据您的需求进行编辑。注意:仅适用于Windows。

/**
 * Java Finder by petrucio@stackoverflow(828681) is licensed under a Creative Commons Attribution 3.0 Unported License.
 * Needs WinRegistry.java. Get it at: https://dev59.com/InVD5IYBdhLWcg3wKoaH
 *
 * JavaFinder - Windows-specific classes to search for all installed versions of java on this system
 * Author: petrucio@stackoverflow (828681)
 *****************************************************************************/

import java.util.*;
import java.io.*;

/**
 * Helper class to fetch the stdout and stderr outputs from started Runtime execs
 * Modified from http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
 *****************************************************************************/
class RuntimeStreamer extends Thread {
    InputStream is;
    String lines;

    RuntimeStreamer(InputStream is) {
        this.is = is;
        this.lines = "";
    }
    public String contents() {
        return this.lines;
    }

    public void run() {
        try {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader    br  = new BufferedReader(isr);
            String line = null;
            while ( (line = br.readLine()) != null) {
                this.lines += line + "\n";
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();  
        }
    }

    /**
     * Execute a command and wait for it to finish
     * @return The resulting stdout and stderr outputs concatenated
     ****************************************************************************/
    public static String execute(String[] cmdArray) {
        try {
            Runtime runtime = Runtime.getRuntime();
            Process proc = runtime.exec(cmdArray);
            RuntimeStreamer outputStreamer = new RuntimeStreamer(proc.getInputStream());
            RuntimeStreamer errorStreamer  = new RuntimeStreamer(proc.getErrorStream());
            outputStreamer.start();
            errorStreamer.start();
            proc.waitFor();
            return outputStreamer.contents() + errorStreamer.contents();
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return null;
    }
    public static String execute(String cmd) {
        String[] cmdArray = { cmd };
        return RuntimeStreamer.execute(cmdArray);
    }
}

/**
 * Helper struct to hold information about one installed java version
 ****************************************************************************/
class JavaInfo {
    public String  path;        //! Full path to java.exe executable file
    public String  version;     //! Version string. "Unkown" if the java process returned non-standard version string
    public boolean is64bits;    //! true for 64-bit javas, false for 32

    /**
     * Calls 'javaPath -version' and parses the results
     * @param javaPath: path to a java.exe executable
     ****************************************************************************/
    public JavaInfo(String javaPath) {
        String versionInfo = RuntimeStreamer.execute( new String[] { javaPath, "-version" } );
        String[] tokens = versionInfo.split("\"");

        if (tokens.length < 2) this.version = "Unkown";
        else this.version = tokens[1];
        this.is64bits = versionInfo.toUpperCase().contains("64-BIT");
        this.path     = javaPath;
    }

    /**
     * @return Human-readable contents of this JavaInfo instance
     ****************************************************************************/
    public String toString() {
        return this.path + ":\n  Version: " + this.version + "\n  Bitness: " + (this.is64bits ? "64-bits" : "32-bits");
    }
}

/**
 * Windows-specific java versions finder
 *****************************************************************************/
public class JavaFinder {

    /**
     * @return: A list of javaExec paths found under this registry key (rooted at HKEY_LOCAL_MACHINE)
     * @param wow64  0 for standard registry access (32-bits for 32-bit app, 64-bits for 64-bits app)
     *               or WinRegistry.KEY_WOW64_32KEY to force access to 32-bit registry view,
     *               or WinRegistry.KEY_WOW64_64KEY to force access to 64-bit registry view
     * @param previous: Insert all entries from this list at the beggining of the results
     *************************************************************************/
    private static List<String> searchRegistry(String key, int wow64, List<String> previous) {
        List<String> result = previous;
        try {
            List<String> entries = WinRegistry.readStringSubKeys(WinRegistry.HKEY_LOCAL_MACHINE, key, wow64);
            for (int i = 0; entries != null && i < entries.size(); i++) {
                String val = WinRegistry.readString(WinRegistry.HKEY_LOCAL_MACHINE, key + "\\" + entries.get(i), "JavaHome", wow64);
                if (!result.contains(val + "\\bin\\java.exe")) {
                    result.add(val + "\\bin\\java.exe");
                }
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return result;
    }

    /**
     * @return: A list of JavaInfo with informations about all javas installed on this machine
     * Searches and returns results in this order:
     *   HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment (32-bits view)
     *   HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment (64-bits view)
     *   HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Development Kit     (32-bits view)
     *   HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Development Kit     (64-bits view)
     *   WINDIR\system32
     *   WINDIR\SysWOW64
     ****************************************************************************/
    public static List<JavaInfo> findJavas() {
        List<String> javaExecs = new ArrayList<String>();

        javaExecs = JavaFinder.searchRegistry("SOFTWARE\\JavaSoft\\Java Runtime Environment", WinRegistry.KEY_WOW64_32KEY, javaExecs);
        javaExecs = JavaFinder.searchRegistry("SOFTWARE\\JavaSoft\\Java Runtime Environment", WinRegistry.KEY_WOW64_64KEY, javaExecs);
        javaExecs = JavaFinder.searchRegistry("SOFTWARE\\JavaSoft\\Java Development Kit",     WinRegistry.KEY_WOW64_32KEY, javaExecs);
        javaExecs = JavaFinder.searchRegistry("SOFTWARE\\JavaSoft\\Java Development Kit",     WinRegistry.KEY_WOW64_64KEY, javaExecs);

        javaExecs.add(System.getenv("WINDIR") + "\\system32\\java.exe");
        javaExecs.add(System.getenv("WINDIR") + "\\SysWOW64\\java.exe");

        List<JavaInfo> result = new ArrayList<JavaInfo>();
        for (String javaPath: javaExecs) {
            if (!(new File(javaPath).exists())) continue;
            result.add(new JavaInfo(javaPath));
        }
        return result;
    }

    /**
     * @return: The path to a java.exe that has the same bitness as the OS
     * (or null if no matching java is found)
     ****************************************************************************/
    public static String getOSBitnessJava() {
        String arch      = System.getenv("PROCESSOR_ARCHITECTURE");
        String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432");
        boolean isOS64 = arch.endsWith("64") || (wow64Arch != null && wow64Arch.endsWith("64"));

        List<JavaInfo> javas = JavaFinder.findJavas();
        for (int i = 0; i < javas.size(); i++) {
            if (javas.get(i).is64bits == isOS64) return javas.get(i).path;
        }
        return null;
    }

    /**
     * Standalone testing - lists all Javas in the system
     ****************************************************************************/
    public static void main(String [] args) {
        List<JavaInfo> javas = JavaFinder.findJavas();
        for (int i = 0; i < javas.size(); i++) {
            System.out.println("\n" + javas.get(i));
        }
    }
}

你还需要更新的WinRegistry.java文件,以便从Windows注册表的32位和64位部分读取值:https://dev59.com/InVD5IYBdhLWcg3wKoaH#11854901 我通常不是Java程序员,所以我的代码可能不遵循Java惯例。请原谅。
这是我在Win 7 64位机器上的一个示例运行:
>java JavaFinder

C:\Program Files (x86)\Java\jre6\bin\java.exe:
  Version: 1.6.0_31
  Bitness: 32-bits

C:\Program Files\Java\jre6\bin\java.exe:
  Version: 1.6.0_31
  Bitness: 64-bits

D:\Dev\Java\jdk1.6.0_31\bin\java.exe:
  Version: 1.6.0_31
  Bitness: 64-bits

C:\Windows\system32\java.exe:
  Version: 1.6.0_31
  Bitness: 64-bits

C:\Windows\SysWOW64\java.exe:
  Version: 1.6.0_31
  Bitness: 32-bits

1
我刚刚注意到,使用32位的java.exe运行此程序会将我发送到注册表的不同部分,并显示C:\ Program Files(x86)\ Java \ jre6 \ bin \ java.exe。当这个问题被解决后,我会进行调试并更新我的帖子。 - Petrucio
在Java路径字符串中,不应使用反斜杠。始终使用正斜杠。在底层,文件系统实现将自动转换目录分隔符。 - Weltraumschaf

3
在Windows平台上,您可以调用外部程序来查询是否已安装JRE:
java -version:1.6 -version

如果您已经安装了Java,该命令将返回“java版本”1.6.0_xx“”。如果您未安装它,则会显示“无法找到符合1.6规范的JRE”。

在Linux上似乎不起作用,可能是因为Linux没有标准的安装Java的方式。


看起来这只适用于Hotspot JVM,并且仅检测与运行命令的Java版本相同的位数(32/64)的安装。 - Brad Mace

3

2
基本上没有办法从Java代码中枚举所有JVM,考虑到不同的平台、不同的JDK供应商、从数据库到飞行模拟器和视频渲染引擎等其他产品中捆绑的JDK。大多数JDK只是链接到另一个JDK,例如v.1.2指向已安装的v.6。获取版本的常见方法是:java -version。但我们有一些要点:
  1. 对于Windows,查看注册表HKEY_LOCAL_MACHINE\Software\JavaSoft\。还要考虑64位操作系统的Wow6432Node模式。检查那些使用Java创建软件的主要供应商的产品。
  2. Mac OS X /System/Library/Frameworks/JavaVM.framework/Versions + Open JDK
  3. Linux和其他一些Unix系统-which java, /etc/alternatives/java rpm -qa|grep java等。

2
在Windows命令提示符中,您可以运行以下两个命令(或一起运行为批处理文件)。这将在Windows添加/删除程序(注册表项)中查找并报告找到的JAVA版本及其安装/卸载链接、文件夹路径和其他一些信息。
wmic product where "name LIKE '%%java%%'" get * /format:textvaluelist > temp_javavers.txt 

notepad.exe temp_javavers.txt

1

installed这个词有点含糊不清 - 即使JRE和JDK随着一个安装程序一起提供,实际上我们只需要将JRE或JDK的文件复制到机器上,就可以立即使用它。

因此,通常您需要查找本地计算机上所有的javajava.exe可执行文件,并调用java -version来查看1,这个名为java/java.exe的可执行文件是否真的是(部分)JRE。


刚看到你询问了JVM并想要调用编译器。如果你正在寻找JDK,请使用上述方法,但查找所有的javac/javac.exe。它们也有-version选项。

1这种方法存在“不冒险,不好玩”的风险,请仔细查看Sean的评论!如果您不能信任“机器”(或其“用户”),则可能需要测试可执行文件是脚本/批处理还是二进制文件 - 即使二进制文件也可能擦除您的磁盘,即使原始的javac可执行文件也可能被一些恶意代码替换...


4
在本地计算机上查找所有的Java或Java.exe可执行文件,并调用java -version命令。这很糟糕,您不能在整个系统上随意执行二进制文件!如果有人在某个地方放置了一个名为“java”的shell脚本,并包含内容“rm -rf /”,那会怎么样? - Sean Patrick Floyd
1
@Sean Patrick Floyd - 但这是一个普遍的困境:除非您不知道所有原始java / javac可执行文件的校验和,否则您将必须执行可执行文件以查看它是否真的是java / javac二进制文件...知道测试可能会炸毁您的系统(或添加一些微小的恶意软件)。 - Andreas Dolk

0

那个建议相当陈旧,而且只适用于小程序。请在此处查看有关指定JWS版本的详细信息。对于小程序,我倾向于使用deployJava.js来确保最低限度的Java可用性。deployJava.js也可用于编写JWS应用程序的启动按钮。 - Andrew Thompson

0
使用特定的JDK版本编译一些源代码。使用最新JDK中可用的JavaCompiler,并为-source-target-bootclasspath设置适当的选项。后两个选项是javac交叉编译选项之一。
至于找JDK,弹出一个JFileChooser,将当前JRE路径作为默认目录。如果用户无法从那里导航到JDK,则他们可能不应该编写代码。

0

要查找已安装的Java版本,比寻找javac.exe更好的方法是检查注册表。Java会创建一个名为HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment的注册表键,其中包含一个字符串CurrentVersion,其值为版本号,例如1.5或1.6。

您可以使用该信息来查找JVM:

  • HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\1.5\JavaHome = C:\Program Files\Java\j2re1.5
  • HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\1.5\RuntimeLib = C:\Program Files\Java\j2re1.4.2\bin\client\jvm.dll

您可以在此处查看更多信息:http://java.sun.com/j2se/1.4.2/runtime_win32.html

我不熟悉如何从Java中访问注册表,但您始终可以运行regedit的命令行界面并解析结果,这就是我所做的。


@Andrew,java.sun.com网页列出了Windows和Solaris的信息,但没有列出Mac或Unix的信息。这里有一个关于如何在Mac上找到Java的页面:http://developer.apple.com/library/mac/#qa/qa1170/_index.html。在Unix上,可能需要检查JVM本身的文件夹;我不确定。 - Steven

0

如先前所述,HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment 应列出可用的 JVM,但我刚刚发现,尽管此注册表目前仅在我的计算机上列出了一个 64 位的 1.6.0_31 JVM,但我还安装了一个 32 位的 java 在 C:\Windows\SysWOW64 下。

因此,注册表中列出的信息并不能完全反映实际情况,如果您想在 64 位 Windows 上运行 32 位 JVM,则还应尝试使用 C:\Windows\SysWOW64


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接