问题描述
我想从我的 java 应用程序调用 NetworkExtension 框架函数。作为第一步,我调用 NETunnelProviderManager.loadAllFromPreferencesWithCompletionHandler。我正在使用从源代码 (0.8.3-SNAPSHOT) 编译的 Rococoa library。
我的 NETunnelProviderManager 类如下:
package ai.safekids.infra.jna.ext.mac.networkextension;
import ai.safekids.infra.jna.ext.mac.data.NSArrayRef;
import ai.safekids.infra.jna.ext.mac.data.NSErrorRef;
import com.sun.jna.Callback;
import org.rococoa.rococoa;
import org.rococoa.Objcclass;
import org.rococoa.cocoa.foundation.NSArray;
public abstract class NETunnelProviderManager extends NEVPNManager {
public static final _Class CLASS = rococoa.createClass("NETunnelProviderManager",_Class.class);
public interface LoadAllFromPreferencesCompletionHandler extends Callback {
void callback(NSArrayRef managers,NSErrorRef error);
}
public static NETunnelProviderManager new_() {
return rococoa.create("NETunnelProviderManager",NETunnelProviderManager.class);
}
public interface _Class extends Objcclass {
void loadAllFromPreferencesWithCompletionHandler(LoadAllFromPreferencesCompletionHandler completionHandler);
}
public static void loadAllFromPreferencesWithCompletionHandler(LoadAllFromPreferencesCompletionHandler completionHandler) {
CLASS.loadAllFromPreferencesWithCompletionHandler(completionHandler);
}
}
和 NEVPNManager 类是:
package ai.safekids.infra.jna.ext.mac.networkextension;
import com.sun.jna.Callback;
import org.rococoa.rococoa;
import org.rococoa.cocoa.foundation.NSError;
import org.rococoa.cocoa.foundation.NSObject;
import org.rococoa.Objcclass;
public abstract class NEVPNManager extends NSObject {
public static final _Class CLASS = rococoa.createClass("NEVPNManager",_Class.class);
public interface LoadFromPreferencesCompletionHandler extends Callback {
void invoke(NSError error);
}
public interface _Class extends Objcclass {
NEVPNManager sharedManager();
}
public static NEVPNManager new_() {
return rococoa.create("NEVPNManager",NEVPNManager.class);
}
static public final NEVPNManager sharedManager = NEVPNManager.CLASS.sharedManager();
static public NEVPNManager sharedManager() {
return sharedManager;
}
public abstract void loadFromPreferencesWithCompletionHandler(LoadFromPreferencesCompletionHandler completionHandler);
}
导致虚拟机崩溃的单元测试是:
public void testLoadAllPreferences() throws Exception {
NETunnelProviderManager.LoadAllFromPreferencesCompletionHandler completionHandler = new NETunnelProviderManager.LoadAllFromPreferencesCompletionHandler() {
public void callback(NSArrayRef managers,NSErrorRef error) {
Assert.assertEquals(true,true);
}
};
NETunnelProviderManager.loadAllFromPreferencesWithCompletionHandler(completionHandler);
TimeUnit.SECONDS.sleep(5);
}
我的 NSArrayRef 类是
package ai.safekids.infra.jna.ext.mac.data;
import org.rococoa.ObjCObjectByReference;
public class NSArrayRef extends ObjCObjectByReference {
}
mvn test
的输出是
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running ai.safekids.infra.jna.ext.mac.NETunnelProviderManagerTest
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/Users/mac/.m2/repository/cglib/cglib/3.3.0/cglib-3.3.0.jar) to method java.lang.classLoader.defineClass(java.lang.String,byte[],int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
#
# A Fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0x00007fff2049ebc3,pid=12628,tid=5891
#
# JRE version: OpenJDK Runtime Environment (15.0.2+7) (build 15.0.2+7)
# Java VM: OpenJDK 64-Bit Server VM (15.0.2+7,mixed mode,sharing,tiered,compressed oops,g1 gc,bsd-amd64)
# Problematic frame:
# C [libobjc.A.dylib+0x5bc3] objc_retain+0x23
#
# No core dump will be written. Core dumps have been disabled. To enable core dumping,try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /Users/mac/code/safekids/packet-proxy/hs_err_pid12628.log
#
# If you would like to submit a bug report,please visit:
# https://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
/bin/sh: line 1: 12628 Abort trap: 6 /usr/local/Cellar/openjdk/15.0.2/libexec/openjdk.jdk/Contents/Home/bin/java -jar /Users/mac/code/safekids/packet-proxy/target/surefire/surefirebooter783506685843365516.jar /Users/mac/code/safekids/packet-proxy/target/surefire/surefire517337276897757629tmp /Users/mac/code/safekids/packet-proxy/target/surefire/surefire_011227232066576961507tmp
Results :
Tests run: 0,Failures: 0,Errors: 0,Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.094 s
[INFO] Finished at: 2021-05-25T16:31:12+05:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project packet-proxy: Execution default-test of goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test Failed: The forked VM terminated without saying properly goodbye. VM crash or System.exit called ? -> [Help 1]
几个问题:
- rococoa 库是否支持回调?
- VM 崩溃的原因是什么?我该如何解决?
- 我应该在 LoadAllFromPreferencesCompletionHandler 或 NSErrorRef/NSArrayRef
解决方法
Rococoa 库主要只是底层 JNA 库的面向对象接口,它支持回调。
JVM 崩溃的可能原因是您的回调超出了可达范围。只要在 Java 中执行此行,JVM 编译器就会认为您已完成 completionHandler
变量的处理,并且它可以进行垃圾回收。
NETunnelProviderManager.loadAllFromPreferencesWithCompletionHandler(completionHandler);
如果您使用的是 JDK 9+,则使用 Reachability Fence 最容易解决此问题。只需在回调后添加此行即可确保保留强引用:
Reference.reachabilityFence(completionHandler);
如果您使用的是 JDK8 或更早版本,最好的解决方法是使用 try ... finally 块并对 finally 块中的对象执行某些操作,例如,
try {
NETunnelProviderManager.loadAllFromPreferencesWithCompletionHandler(completionHandler);
TimeUnit.SECONDS.sleep(5);
} finally {
// do something with completionHandler
}
“做某事”可以是一个微不足道的执行(例如,toString()),您甚至不需要输出,但会确保 Java 保留对象。
我在 OpenJDK 8(热点)中读到的一种可能的“做某事”是在别处定义另一种方法,该方法将类似于 JDK9+ 的可达性栅栏。我没有验证过;请自行承担风险尝试。这可能不适用于其他 JVM。
static void reachabilityFence(Object obj) {
// do nothing
}
另一种可能的解决方法是使您的回调可关闭,将其创建放在 try-with-resources 块中,该块将保留引用,直到块完成并调用 close()
(可以是无操作方法)。