私は次のコードでプロセスを開始しました
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "path");
try {
Process p = pb.start();
}
catch (IOException ex) {}
ここで、開始したばかりのプロセスのPIDを知る必要があります。
このためのパブリックAPIはまだありません。サンを参照してください バグ4244896 、サン バグ4250622
回避策として:
Runtime.exec(...)
タイプのオブジェクトを返します
Java.lang.Process
Processクラスは抽象的であり、返されるのは、オペレーティングシステム用に設計されたProcessのサブクラスです。たとえば、Macでは、Java.lang.UnixProcess
pid
というプライベートフィールドがあります。 Reflectionを使用すると、このフィールドの値を簡単に取得できます。これは明らかにハッキングですが、役立つかもしれません。とにかくPID
は何が必要ですか?
このページにはHOWTOがあります:
http://www.golesny.de/p/code/javagetpid
Windowsの場合:
Runtime.exec(..)
「Java.lang.Win32Process」のインスタンスを返します)OR "Java.lang.ProcessImpl"
両方にプライベートフィールド「ハンドル」があります。
これは、プロセスのOSハンドルです。 PIDを照会するには、この+ Win32 APIを使用する必要があります。そのページには、その方法の詳細が記載されています。
Java 9クラスProcess
には新しいメソッド long pid()
があるため、シンプルな
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "path");
try {
Process p = pb.start();
long pid = p.pid();
} catch (IOException ex) {
// ...
}
Unixシステム(LinuxおよびMac)
public static synchronized long getPidOfProcess(Process p) {
long pid = -1;
try {
if (p.getClass().getName().equals("Java.lang.UNIXProcess")) {
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
pid = f.getLong(p);
f.setAccessible(false);
}
} catch (Exception e) {
pid = -1;
}
return pid;
}
ライブラリに jna (「JNA」と「JNA Platform」の両方)を含め、この関数を使用します。
import com.Sun.jna.Pointer;
import com.Sun.jna.platform.win32.Kernel32;
import com.Sun.jna.platform.win32.WinNT;
import Java.lang.reflect.Field;
public static long getProcessID(Process p)
{
long result = -1;
try
{
//for windows
if (p.getClass().getName().equals("Java.lang.Win32Process") ||
p.getClass().getName().equals("Java.lang.ProcessImpl"))
{
Field f = p.getClass().getDeclaredField("handle");
f.setAccessible(true);
long handl = f.getLong(p);
Kernel32 kernel = Kernel32.INSTANCE;
WinNT.HANDLE hand = new WinNT.HANDLE();
hand.setPointer(Pointer.createConstant(handl));
result = kernel.GetProcessId(hand);
f.setAccessible(false);
}
//for unix based operating systems
else if (p.getClass().getName().equals("Java.lang.UNIXProcess"))
{
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
result = f.getLong(p);
f.setAccessible(false);
}
}
catch(Exception ex)
{
result = -1;
}
return result;
}
私は、ほとんどのプラットフォームで作業している間、まったく防弾に見えるソリューションを見つけたと思います。アイデアは次のとおりです。
子プロセスのみをチェックするため、同じマシン内の他のプロセスに誤用されることはありません。 JVM全体のミューテックスにより、新しいプロセスが正しいものであることを確認できます。
子プロセスリストの読み取りは、WindowsでWIN API呼び出しを必要としないため、プロセスオブジェクトからPIDを取得するよりも簡単です。さらに重要なことは、いくつかのライブラリで既に行われていることです。
以下は、 JavaSysMon ライブラリを使用した上記のアイデアの実装です。それ
class UDKSpawner {
private int uccPid;
private Logger uccLog;
/**
* Mutex that forces only one child process to be spawned at a time.
*
*/
private static final Object spawnProcessMutex = new Object();
/**
* Spawns a new UDK process and sets {@link #uccPid} to it's PID. To work correctly,
* the code relies on the fact that no other method in this JVM runs UDK processes and
* that no method kills a process unless it acquires lock on spawnProcessMutex.
* @param procBuilder
* @return
*/
private Process spawnUDK(ProcessBuilder procBuilder) throws IOException {
synchronized (spawnProcessMutex){
JavaSysMon monitor = new JavaSysMon();
DirectUDKChildProcessVisitor beforeVisitor = new DirectUDKChildProcessVisitor();
monitor.visitProcessTree(monitor.currentPid(), beforeVisitor);
Set<Integer> alreadySpawnedProcesses = beforeVisitor.getUdkPids();
Process proc = procBuilder.start();
DirectUDKChildProcessVisitor afterVisitor = new DirectUDKChildProcessVisitor();
monitor.visitProcessTree(monitor.currentPid(), afterVisitor);
Set<Integer> newProcesses = afterVisitor.getUdkPids();
newProcesses.removeAll(alreadySpawnedProcesses);
if(newProcesses.isEmpty()){
uccLog.severe("There is no new UKD PID.");
}
else if(newProcesses.size() > 1){
uccLog.severe("Multiple new candidate UDK PIDs");
} else {
uccPid = newProcesses.iterator().next();
}
return proc;
}
}
private void killUDKByPID(){
if(uccPid < 0){
uccLog.severe("Cannot kill UCC by PID. PID not set.");
return;
}
synchronized(spawnProcessMutex){
JavaSysMon monitor = new JavaSysMon();
monitor.killProcessTree(uccPid, false);
}
}
private static class DirectUDKChildProcessVisitor implements ProcessVisitor {
Set<Integer> udkPids = new HashSet<Integer>();
@Override
public boolean visit(OsProcess op, int i) {
if(op.processInfo().getName().equals("UDK.exe")){
udkPids.add(op.processInfo().getPid());
}
return false;
}
public Set<Integer> getUdkPids() {
return udkPids;
}
}
}
私のテストでは、すべてのIMPLクラスに「pid」フィールドがありました。これは私のために働いています:
public static int getPid(Process process) {
try {
Class<?> cProcessImpl = process.getClass();
Field fPid = cProcessImpl.getDeclaredField("pid");
if (!fPid.isAccessible()) {
fPid.setAccessible(true);
}
return fPid.getInt(process);
} catch (Exception e) {
return -1;
}
}
戻り値が-1でないことを確認してください。そうであれば、ps
の出力を解析します。
非常に簡単なProcess
オブジェクトからUNIX PIDを取得するために、移植性のないアプローチを使用しました。
ステップ1:いくつかのReflection API呼び出しを使用して、ターゲットサーバーJRE上のProcess
実装クラスを識別します(Process
は抽象クラスであることに注意してください)。 UNIX実装が私の実装に似ている場合、プロセスのPIDを含むpid
という名前のプロパティを持つ実装クラスが表示されます。これが、私が使用したロギングコードです。
//--------------------------------------------------------------------
// Jim Tough - 2014-11-04
// This temporary Reflection code is used to log the name of the
// class that implements the abstract Process class on the target
// JRE, all of its 'Fields' (properties and methods) and the value
// of each field.
//
// I only care about how this behaves on our UNIX servers, so I'll
// deploy a snapshot release of this code to a QA server, run it once,
// then check the logs.
//
// TODO Remove this logging code before building final release!
final Class<?> clazz = process.getClass();
logger.info("Concrete implementation of " + Process.class.getName() +
" is: " + clazz.getName());
// Array of all fields in this class, regardless of access level
final Field[] allFields = clazz.getDeclaredFields();
for (Field field : allFields) {
field.setAccessible(true); // allows access to non-public fields
Class<?> fieldClass = field.getType();
StringBuilder sb = new StringBuilder(field.getName());
sb.append(" | type: ");
sb.append(fieldClass.getName());
sb.append(" | value: [");
Object fieldValue = null;
try {
fieldValue = field.get(process);
sb.append(fieldValue);
sb.append("]");
} catch (Exception e) {
logger.error("Unable to get value for [" +
field.getName() + "]", e);
}
logger.info(sb.toString());
}
//--------------------------------------------------------------------
STEP 2: Reflectionロギングから取得した実装クラスとフィールド名に基づいて、Process
実装クラスをスリするコードを記述し、Reflection APIを使用してそこからPIDを取得します。以下のコードは、UNIXのフレーバーで機能します。 EXPECTED_IMPL_CLASS_NAME
およびEXPECTED_PID_FIELD_NAME
定数を調整して、機能させる必要がある場合があります。
/**
* Get the process id (PID) associated with a {@code Process}
* @param process {@code Process}, or null
* @return Integer containing the PID of the process; null if the
* PID could not be retrieved or if a null parameter was supplied
*/
Integer retrievePID(final Process process) {
if (process == null) {
return null;
}
//--------------------------------------------------------------------
// Jim Tough - 2014-11-04
// NON PORTABLE CODE WARNING!
// The code in this block works on the company UNIX servers, but may
// not work on *any* UNIX server. Definitely will not work on any
// Windows Server instances.
final String EXPECTED_IMPL_CLASS_NAME = "Java.lang.UNIXProcess";
final String EXPECTED_PID_FIELD_NAME = "pid";
final Class<? extends Process> processImplClass = process.getClass();
if (processImplClass.getName().equals(EXPECTED_IMPL_CLASS_NAME)) {
try {
Field f = processImplClass.getDeclaredField(
EXPECTED_PID_FIELD_NAME);
f.setAccessible(true); // allows access to non-public fields
int pid = f.getInt(process);
return pid;
} catch (Exception e) {
logger.warn("Unable to get PID", e);
}
} else {
logger.warn(Process.class.getName() + " implementation was not " +
EXPECTED_IMPL_CLASS_NAME + " - cannot retrieve PID" +
" | actual type was: " + processImplClass.getName());
}
//--------------------------------------------------------------------
return null; // If PID was not retrievable, just return null
}
これは一般的な答えではありません。
ただし、一部のプログラム、特にサービスや実行時間の長いプログラムでは、「pidファイル」を作成(またはオプションで作成を提案)します。
たとえば、LibreOfficeは--pidfile={file}
、 docs を参照してください。
Java/Linuxソリューションを探していましたが、PIDは(私の場合)手元にありました。
簡単な解決策はありません。私が過去にやった方法は、Unixライクなシステムでps
コマンド、またはWindowsでtasklist
コマンドを実行するために別のプロセスを開始し、その後の出力を解析することです。必要なPIDのコマンド。実際には、PIDを返したプラットフォームごとに個別のシェルスクリプトにそのコードを入れて、Javaをプラットフォームに依存しないようにできるようにしました。これはできません。短時間のタスクではうまく機能しますが、それは私にとっては問題ではありませんでした。
GNU/Linux&MacOS(または一般的にUNIXのような)システムの場合、私は以下の方法を使用しました。
private int tryGetPid(Process process)
{
if (process.getClass().getName().equals("Java.lang.UNIXProcess"))
{
try
{
Field f = process.getClass().getDeclaredField("pid");
f.setAccessible(true);
return f.getInt(process);
}
catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException | SecurityException e)
{
}
}
return 0;
}
これを行う唯一の移植可能な方法は、(子)プロセスを別の(親)Javaプロセスを通して実行することです。これにより、親プロセスの実際のPIDが通知されます。子プロセス何でもかまいません。
このラッパーのコードは
package com.panayotis.wrapper;
import Java.io.File;
import Java.io.IOException;
import Java.lang.management.ManagementFactory;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
System.out.println(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]);
ProcessBuilder pb = new ProcessBuilder(args);
pb.directory(new File(System.getProperty("user.dir")));
pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.start().waitFor();
}
}
これを使用するには、このファイルだけでjarファイルを作成し、コマンド引数で呼び出します:
String Java = System.getProperty("Java.home") + separator + "bin" + separator + "Java.exe";
String jar_wrapper = "path\\of\\wrapper.jar";
String[] args = new String[]{Java, "-cp", jar_wrapper, "com.panayotis.wrapper.Main", actual_exec_args...);
jnr-process プロジェクトはこの機能を提供します。
これは、jrubyで使用されるJavaネイティブランタイムの一部であり、将来のプロトタイプと見なすことができます Java-FFI
1つの解決策は、プラットフォームが提供する特異なツールを使用することです。
private static String invokeLinuxPsProcess(String filterByCommand) {
List<String> args = Arrays.asList("ps -e -o stat,pid,unit,args=".split(" +"));
// Example output:
// Sl 22245 bpds-api.service /opt/libreoffice5.4/program/soffice.bin --headless
// Z 22250 - [soffice.bin] <defunct>
try {
Process psAux = new ProcessBuilder(args).redirectErrorStream(true).start();
try {
Thread.sleep(100); // TODO: Find some passive way.
} catch (InterruptedException e) { }
try (BufferedReader reader = new BufferedReader(new InputStreamReader(psAux.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
if (!line.contains(filterByCommand))
continue;
String[] parts = line.split("\\w+");
if (parts.length < 4)
throw new RuntimeException("Unexpected format of the `ps` line, expected at least 4 columns:\n\t" + line);
String pid = parts[1];
return pid;
}
}
}
catch (IOException ex) {
log.warn(String.format("Failed executing %s: %s", args, ex.getMessage()), ex);
}
return null;
}
免責事項:テストされていませんが、あなたはアイデアを得る:
ps
を呼び出してプロセスをリストし、ps
でリストします。移植性が問題にならず、Windowsのすべての最新バージョンで動作することがテスト済みであることがわかっているコードを使用しながら、Windowsでpidを手間をかけずに取得したい場合は、kohsukeの winp を使用できます。 =ライブラリ。 Maven Centralでも簡単に使用できます。
Process process = //...;
WinProcess wp = new WinProcess(process);
int pid = wp.getPid();
そのような機能を備えたオープンソースのライブラリがあり、クロスプラットフォームの実装があります: https://github.com/OpenHFT/Java-Thread-Affinity
PIDを取得するだけではやり過ぎかもしれませんが、CPUやスレッドID、特にスレッドアフィニティなどの他のものが必要な場合は、それで十分かもしれません。
現在のスレッドのPIDを取得するには、Affinity.getAffinityImpl().getProcessId()
を呼び出すだけです。
これは、JNAを使用して実装されます(arcsinの回答を参照)。