これは何?
今回、パッチを書いたのはJDK-8219904
のClassCastException when calling FlightRecorderMXBean#getRecordings()
です。
[JDK-8219904] ClassCastException when calling FlightRecorderMXBean#getRecordings() - Java Bug System
これはJMXのリモート接続でJFRの一覧を取得しようとするとClassCastException
が発生するというバグです。
どんな実装だったのか
原因としてはリモートでJMXで接続してJFRの一覧を取得しようとするとCompositeData
からデータを取り出してRecordingInfo
を作ります。その時に上記例外が発生します。具体的にはid
の型がlong
なのにint
にキャストしてClassCastException
は起きます。また、仕様上toDisk
という要素名なのに、実装はdisk
という名前になっているためそこでも問題が発生します。
原因としては、ローカル接続でのテストはやっていましたが、リモート接続でのテストをやっていなかったことです。
どう直したの?
基本的には、型と名前を変えるだけでOK。ただし、このバグが直っていないJMXクライアントからdisk
という要素名を使って接続してくる可能性があるため、そこも注意する必要があります。
また、再発防止のため、自プロセスへリモート接続をしてJFRの記録を起動/一覧を取得/停止するテストを追加しました。しかし、これは普通にやると自分自身にはアタッチできないため実行できません。そのため、自分自身にアタッチできるようにするため-Djdk.attach.allowAttachSelf=true
というVM引数を追加します。
パッチはこちら
パッチの抜粋はこちらです。
--- a/src/jdk.management.jfr/share/classes/jdk/management/jfr/RecordingInfo.java Mon Feb 24 23:24:14 2020 +0100 +++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/RecordingInfo.java Tue Feb 25 03:28:31 2020 +0100 @@ -1,5 +1,5 @@ //略 @@ -51,7 +51,7 @@ private final String state; private final boolean dumpOnExit; private final long size; - private final boolean disk; + private final boolean toDisk; private final long maxAge; private final long maxSize; private final long startTime; @@ -67,7 +67,7 @@ state = recording.getState().toString(); dumpOnExit = recording.getDumpOnExit(); size = recording.getSize(); - disk = recording.isToDisk(); + toDisk = recording.isToDisk(); Duration d = recording.getMaxAge(); if (d == null) { @@ -87,12 +87,17 @@ } private RecordingInfo(CompositeData cd) { - id = (int) cd.get("id"); + id = (long) cd.get("id"); name = (String) cd.get("name"); state = (String) cd.get("state"); dumpOnExit = (boolean) cd.get("dumpOnExit"); size = (long) cd.get("size"); - disk = (boolean) cd.get("disk"); + if(cd.containsKey("toDisk")){ + toDisk = (boolean) cd.get("toDisk"); + } else { + // Before JDK-8219904 was fixed, the element name was disk, so for compatibility + toDisk = (boolean) cd.get("disk"); + } maxAge = (Long) cd.get("maxAge"); maxSize = (Long) cd.get("maxSize"); startTime = (Long) cd.get("startTime"); @@ -290,7 +295,7 @@ * @return {@code true} if recording is to disk, {@code false} otherwise */ public boolean isToDisk() { - return disk; + return toDisk; } /** @@ -342,7 +347,7 @@ * <td>{@code Long}</td> * </tr> * <tr> - * <th scope="row">disk</th> + * <th scope="row">toDisk</th> * <td>{@code Boolean}</td> * </tr> * <tr> --- a/test/jdk/jdk/jfr/jmx/JmxHelper.java Mon Feb 24 23:24:14 2020 +0100 +++ b/test/jdk/jdk/jfr/jmx/JmxHelper.java Tue Feb 25 03:28:31 2020 +0100 @@ -36,6 +36,7 @@ //略 @@ -52,7 +53,15 @@ //略 + public class JmxHelper { + private static final String LOCAL_CONNECTION_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress"; public static RecordingInfo getJmxRecording(long recId) { for (RecordingInfo r : getFlighteRecorderMXBean().getRecordings()) { @@ -279,4 +288,18 @@ return ManagementFactory.getPlatformMXBean(FlightRecorderMXBean.class); } + public static long getPID(){ + return ManagementFactory.getRuntimeMXBean().getPid(); + } + + public static FlightRecorderMXBean getFlighteRecorderMXBean(long pid) throws Exception { + VirtualMachine targetVM = VirtualMachine.attach("" + pid); + String jmxServiceUrl = targetVM.getAgentProperties().getProperty(LOCAL_CONNECTION_ADDRESS); + JMXServiceURL jmxURL = new JMXServiceURL(jmxServiceUrl); + JMXConnector connector = JMXConnectorFactory.connect(jmxURL); + MBeanServerConnection connection = connector.getMBeanServerConnection(); + + ObjectName objectName = new ObjectName("jdk.management.jfr:type=FlightRecorder"); + return JMX.newMXBeanProxy(connection, objectName, FlightRecorderMXBean.class); + } } --- a/test/jdk/jdk/jfr/jmx/TestGetRecordings.java Mon Feb 24 23:24:14 2020 +0100 +++ b/test/jdk/jdk/jfr/jmx/TestGetRecordings.java Tue Feb 25 03:28:31 2020 +0100 @@ -1,5 +1,5 @@ //略 @@ -35,7 +35,7 @@ * @key jfr * @requires vm.hasJFR * @library /test/lib /test/jdk - * @run main/othervm jdk.jfr.jmx.TestGetRecordings + * @run main/othervm -Djdk.attach.allowAttachSelf=true -Dcom.sun.management.jmxremote jdk.jfr.jmx.TestGetRecordings */ public class TestGetRecordings { public static void main(String[] args) throws Throwable { @@ -46,5 +46,11 @@ JmxHelper.verifyNotExists(recId, preCreateRecordings); bean.closeRecording(recId); JmxHelper.verifyNotExists(recId, bean.getRecordings()); + + long selfPID = JmxHelper.getPID(); + FlightRecorderMXBean remoteBean = JmxHelper.getFlighteRecorderMXBean(selfPID); + long remoteRecId = remoteBean.newRecording(); + remoteBean.getRecordings(); + remoteBean.closeRecording(remoteRecId); } }