如何在 Java 的新进程中启动“main”?
Posted
技术标签:
【中文标题】如何在 Java 的新进程中启动“main”?【英文标题】:How can I start a 'main' in a new process in Java? 【发布时间】:2011-08-24 18:04:07 【问题描述】:这个问题很简单。如何在另一个 java 进程中启动 main 方法?现在我这样做:
startOptions = new String[] "java", "-jar", "serverstart.jar";
new ProcessBuilder(startOptions).start();
但他们要求我不要使用外部 .jar 文件。 serverstart.jar明明有main方法,但是不调用.jar文件就可以在另一个进程中调用那个main方法吗?
我在想这样的事情:
new ProcessBuilder(ServerStart.main(startOptions)).start();
但我不知道是否存在类似的东西。
【问题讨论】:
将当前类路径复制为 ProcessBuilder 中的参数是否会有所帮助或被允许? .. System.getProperty("java.class.path") 我不知道,那我该怎么办? 那么您将拥有类路径,您只需将其传递给-cp
并加载java
【参考方案1】:
您可以使用反射(java.lang.reflect 包)来做到这一点。
public static void main(String[] args) throws Exception
Class c = Class.forName("ServerStart");
Class[] argTypes = args.getClass() ;
Method m = c.getMethod("main", argTypes);
Object passedArgv[] = args ;
m.invoke(null, passedArgv);
【讨论】:
这不会启动新进程,也可能不会重新初始化静态和最终变量。 不起作用。您需要自定义ClassLoader
来重新初始化所有变量。【参考方案2】:
不可能从 java 创建一个新的“java”进程,因为两个进程不能共享一个 JVM。 (见question and the accepted answer)。
如果您可以接受创建新的Thread
而不是Process
,则可以使用自定义ClassLoader
。 您可以接近一个新的流程。所有静态和最终字段都将重新初始化!
还要注意"ServerStart
类(对于下面的例子)必须在当前正在执行的JVM的类路径中):
public static void main(String args[]) throws Exception
// start the server
start("ServerStart", "arg1", "arg2");
private static void start(final String classToStart, final String... args)
// start a new thread
new Thread(new Runnable()
public void run()
try
// create the custom class loader
ClassLoader cl = new CustomClassLoader();
// load the class
Class<?> clazz = cl.loadClass(classToStart);
// get the main method
Method main = clazz.getMethod("main", args.getClass());
// and invoke it
main.invoke(null, (Object) args);
catch (Exception e)
e.printStackTrace();
).start();
这是自定义类加载器:
private static class CustomClassLoader extends URLClassLoader
public CustomClassLoader()
super(new URL[0]);
protected java.lang.Class<?> findClass(String name)
throws ClassNotFoundException
try
String c = name.replace('.', File.separatorChar) +".class";
URL u = ClassLoader.getSystemResource(c);
String classPath = ((String) u.getFile()).substring(1);
File f = new File(classPath);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte buff[] = new byte[(int) f.length()];
dis.readFully(buff);
dis.close();
return defineClass(name, buff, 0, buff.length, (CodeSource) null);
catch(Exception e)
throw new ClassNotFoundException(e.getMessage(), e);
【讨论】:
Creating a new "java" process from java is not possible since two processes can't share one JVM.
-- 也许我遗漏了什么,但我不认为这里需要共享一个 JVM。
@Costi,不,但是 ServerStart.main(startOptions) 表明他有可能想从 JVM 中调用主方法。
可以,但这只是一个方法调用,与任何新进程无关。
继承 URLClassLoader 而不是 ClassLoader 不是更好吗?这样你就可以毫不费力地处理 Jars 和类目录了。
@aioobe:我现在明白了,ProcessBuilder
与 main()
方法调用结合确实会造成这种混乱,所以 dacwe 从一开始就澄清这一点是正确的。【参考方案3】:
假设一个带有新类加载器的新线程是不够的(不过我会投票支持这个解决方案),我知道您需要创建一个不同的进程来调用类中的 main 方法,而无需将其声明为“jar main 方法" 在清单文件中——因为您不再有不同的 serverstart.jar。
在这种情况下,您可以简单地调用java -cp $yourClassPath your.package.ServerStart
,就像在没有(或不想使用)清单 Main-Class 时运行任何 java 应用程序一样。
【讨论】:
我像他的一样做到了:startOptions = new String[] "java", "-cp", System.getProperty("user.dir"), "target.classes." + ServerStart.class.getName();
但它给了我java.lang.NoClassDefFoundError: target/classes/sample/plugin/hello_maven_plugin/ ServerStart (wrong name: sample/plugin/hello_maven_plugin/ServerStart)
所以我想我在正确的目录中,但我猜他找不到正确的课程。
假设你的user.dir
是maven项目根目录,target/classes
不应该用作包前缀,而是添加到文件系统路径:new String[] "java", "-cp", System.getProperty("user.dir") + "/target/classes", ServerStart.class.getName()
不错!我现在在正确的班级,但是现在我遇到了他找不到指定的 .jar 文件的问题。在我的 ServerStart 中,他启动了一个从 hsqldb 导入的服务器,但他找不到它。 Caused by: java.lang.ClassNotFoundException: org.hsqldb.Server
但是,依赖于从 maven 项目根目录运行应用程序并不是一个好主意,所以我认为更好的解决方案是简单地重用当前进程的类路径(并使用当前目录作为后备):new String[] "java", "-cp", System.getProperty("java.class.path", "."), ServerStart.class.getName()
我在看到你的回复之前写了我之前的评论,但它似乎也解决了你提到的这个最新问题,因为将使用当前进程的完整类路径。【参考方案4】:
我建议从 java 调用一个 shellscript 并使用它来启动新进程(如果你不能只使用另一个线程的话)。
【讨论】:
【参考方案5】:我将在这里回答如何在没有 spring 的情况下创建多进程应用程序:)。 使用 spring 你可以通过 xml 配置来做到这一点。 多线程是另一回事,这是多进程
如下所示创建一个 JavaProces 类。您可以在您的环境中存储此类的对应方 XML/JSON。然后使用Runtime.getRuntime().exec(processRunnerString);
开始您的流程,
你应该先找到java.exe
,vm args
,然后分别设置-classpath
然后mainClass
和args
。
最后你会得到类似 java JRE\java.exe -classpath .;*;lib* AClass arg1 - Dprop=val
您可以使用JMX
与其他进程通信。
import java.util.Dictionary;
import java.util.List;
public class JavaProcess
private String mainClass;
private Dictionary<String, String> vmParameters;
private List<String> classPath;
private List<String> mainArgs;
public String getMainClass()
return mainClass;
public void setMainClass(String mainClass)
this.mainClass = mainClass;
public Dictionary<String, String> getVmParameters()
return vmParameters;
public void setVmParameters(Dictionary<String, String> vmParameters)
this.vmParameters = vmParameters;
public List<String> getClassPath()
return classPath;
public void setClassPath(List<String> classPath)
this.classPath = classPath;
public List<String> getMainArgs()
return mainArgs;
public void setMainArgs(List<String> mainArgs)
this.mainArgs = mainArgs;
MainRunner 应用程序,您可以从一个 配置文件。我刚刚在这里创建了一个虚拟进程,以防万一 错误我从进程的回调中停止它。
//process
JavaProcess jp = new JavaProcess();
//java class
jp.setMainClass("com.hmg.vidapter.run.DriverLauncher");
//main args[]
List<String> mainArgsList = new ArrayList<String>();
mainArgsList.add("ABC1 ARG2 ARG3 ARGN");
jp.setMainArgs(mainArgsList);
//-classpath
List<String> classPath = new ArrayList<String>();
classPath.add("*");
classPath.add("libs\\*");
classPath.add("repo\\*");
jp.setClassPath(classPath);
//-Dvm args.
Dictionary<String, String> vmArgs = new Hashtable<String, String>();
vmArgs.put("-Dcom.sun.management.jmxremote", "");
vmArgs.put("-Dcom.sun.management.jmxremote.authenticate=false", "");
vmArgs.put("-Dcom.sun.management.jmxremote.port=1453", "");
vmArgs.put("-Dcom.sun.management.jmxremote.ssl=false", "");
jp.setVmParameters(vmArgs);
String params = JSONUtils.convertToJSON(jp);
System.out.println(params);
StringBuilder sb = new StringBuilder("\"" + getJavaExecutablePath()+ "\"");
sb.append(" ");
Enumeration<String> vmaEnum = vmArgs.keys();
while (vmaEnum.hasMoreElements())
String key = vmaEnum.nextElement();
sb.append(key + " ");
String val=vmArgs.get(key);
if(val!=null && !val.isEmpty())
sb.append(val + " ");
sb.append(" -classpath ");
List<String> cps = jp.getClassPath();
for (String cp : cps)
sb.append(cp+";");
sb.append(" ");
sb.append(jp.getMainClass());
sb.append(" ");
List<String> mainArgs = jp.getMainArgs();
for (String ma : mainArgs)
sb.append(ma+" ");
System.out.println(sb.toString());
Process p = Runtime.getRuntime().exec(sb.toString());
//write output
InputStreamReader isrO = new InputStreamReader(p.getInputStream());
BufferedReader brO = new BufferedReader(isrO);
String callBackO = brO.readLine();
if (callBackO!=null)
System.out.println("Application Output: " + callBackO);
//write errput
InputStreamReader isr = new InputStreamReader(p.getErrorStream());
BufferedReader br = new BufferedReader(isr);
String callBack = br.readLine();
if (callBack!=null)
System.err.println("Application Error: "+ callBack);
//you can do whatever you want if you don't wanna stop it
p.destroyForcibly();
从 java.home 环境中确定 java.exe 的位置。变量
private static String getJavaExecutablePath()
String javaHome = System.getProperty("java.home");
File f = new File(javaHome);
f = new File(f, "bin");
f = new File(f, "java.exe");
return f.getAbsolutePath();
【讨论】:
以上是关于如何在 Java 的新进程中启动“main”?的主要内容,如果未能解决你的问题,请参考以下文章