java - 如何正确加载本地库进行 sbt 测试?

标签 java scala java-native-interface sbt classloader

我有一个 sbt 项目和一个 java 类,它静态加载 native 库并包含 native 方法。它看起来像这样:

public class NativeContainer {
  static {
    System.load("/path-to-lib");
  }

  public static native void nativeFunc(int n);
}

我还有一个 Scala 测试,它调用 native 函数,如下所示:

class TestJni extends FunSpec { 
  describe("JNI test") {
    NativeContainer.nativeFunc(5);
  }
}

当我通过 sbt 运行测试一次时,一切正常。然而,在接下来的每次运行中我都会得到:

[error] Could not run test intrinsics.TestJni: java.lang.UnsatisfiedLinkError: Native Library /path-to-lib already loaded in another classloader

加载库以避免这种情况的正确方法是什么?重新启动 sbt 可以工作,但我一直在寻找更灵活的解决方案。

我不使用任何库或插件将 sbtJNI 粘合在一起。

这是完整的堆栈跟踪:

[debug] Running TaskDef(TestJni, org.scalatest.tools.Framework$$anon$1@1d29c60d, false, [SuiteSelector]) java.lang.UnsatisfiedLinkError: Native Library /path-to-lib/libNativeContainer.dylib already loaded in another classloader
        at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1907)
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
        at java.lang.Runtime.load0(Runtime.java:809)
        at java.lang.System.load(System.java:1086)
        at NativeContainer.<clinit>(NativeContainer.java:5)
        at TestJni$$anonfun$1.apply$mcV$sp(TestJni.scala:16)
        at org.scalatest.SuperEngine.registerNestedBranch(Engine.scala:613)
        at org.scalatest.FunSpecLike$class.describe(FunSpecLike.scala:357)
        at org.scalatest.FunSpec.describe(FunSpec.scala:1626)
        at TestJni.<init>(TestJni.scala:7)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at java.lang.Class.newInstance(Class.java:442)
        at org.scalatest.tools.Framework$ScalaTestTask.execute(Framework.scala:646)
        at sbt.TestRunner.runTest$1(TestFramework.scala:76)
        at sbt.TestRunner.run(TestFramework.scala:85)
        at sbt.TestFramework$$anon$2$$anonfun$$init$$1$$anonfun$apply$8.apply(TestFramework.scala:197)
        at sbt.TestFramework$$anon$2$$anonfun$$init$$1$$anonfun$apply$8.apply(TestFramework.scala:197)
        at sbt.TestFramework$.sbt$TestFramework$$withContextLoader(TestFramework.scala:185)
        at sbt.TestFramework$$anon$2$$anonfun$$init$$1.apply(TestFramework.scala:197)
        at sbt.TestFramework$$anon$2$$anonfun$$init$$1.apply(TestFramework.scala:197)
        at sbt.TestFunction.apply(TestFramework.scala:202)
        at sbt.Tests$.sbt$Tests$$processRunnable$1(Tests.scala:239)
        at sbt.Tests$$anonfun$makeSerial$1.apply(Tests.scala:245)
        at sbt.Tests$$anonfun$makeSerial$1.apply(Tests.scala:245)
        at sbt.std.Transform$$anon$3$$anonfun$apply$2.apply(System.scala:44)
        at sbt.std.Transform$$anon$3$$anonfun$apply$2.apply(System.scala:44)
        at sbt.std.Transform$$anon$4.work(System.scala:63)
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226)
        at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226)
        at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
        at sbt.Execute.work(Execute.scala:235)
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226)
        at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226)
        at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159)
        at sbt.CompletionService$$anon$2.call(CompletionService.scala:28)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745) [error] Could not run test TestJni: java.lang.UnsatisfiedLinkError: Native Library /path-to-lib/libNativeContainer.dylib already loaded in another classloader

最佳答案

我猜问题是,默认情况下,sbt 在同一个 JVM 但不同的 ClassLoader 中运行每轮测试,但 JNI 库只能链接一次,不能在多个 ClassLoader 中链接多次。

sbt 有一个设置...

fork := true

...这会导致测试在新的 JVM 中运行,而不是在新的 ClassLoader 下的 sbt JVM 中运行。 (参见 the docs 。)在此设置下,每个 JVM 只会加载您的类一次(因为它们可能在生产场景中),而不会非法尝试通过不同的 ClassLoader 多次链接 JNI 库。这应该可以解决您的问题。

关于java - 如何正确加载本地库进行 sbt 测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44483924/

相关文章:

java - 如何读取资源文件? (谷歌云数据流)

java - 通过名称获取 Swing 组件

java - Atomikos、Java 8 和多个数据库

scala - 如何基于大文本提取字符n-gram

scala - 使用scala下载图像文件

c - JNI 函数在 android 上返回非法的 UTF 字符

java - 什么会导致 Java native 函数(在 C 中)在进入时出现段错误?

java - 具有动态代理对象的 ComboBoxModel

java - 无法打开包含文件 : 'jni.h' : No such file or directory

scala - 在 Scala 中检查 Java 数据类型的空值