JMM 保证在 try-with-resource 和 JNI 调用中重新排序

JMM guarantees about reordering within a try-with-resource & JNI calls

给定以下代码示例:

 try (AutoClosable closable = new XXX()) {
      o.method1(closable);  
      o.method2();
 }

Java 内存模型是否允许 HotSpot 在 o.method2() 之前重新排序 closable.close()

我故意省略实施细节,例如 does method1 captures closable? 在这个问题的第一部分。


我的具体用例是这样的:

我有一个 C 库,可以概括为:

static char* value;

void capture(char* ptr){
        value = ptr;
}

int len(void) {
    return strlen(value);
}

本机库使用 JNA 包装

interface CApi extends Library {

 static {
   Native.register("test.so", CApi.class)
 }

  void capture(Pointer s);
  int test();
}

我原来的客户端代码是这样的:

cApi = Native.loadLibrary("test.so", CApi.class);


byte[] data = Native.toByteArray("foo");
Memory m = new Memory(data.length + 1);
m.write(0, data, 0, data.length);
m.setByte(data.length, (byte)0);

cApi.capture(m);
System.out.print(cApi.len());

第一个版本的问题是 m 是从 Java 分配的,并且此内存的生命周期与 m 强可达有关。 m 一旦 capture(m) 就不再是强可达的,当 GC 启动时内存将被释放(JNA 依赖 finalize 来释放本机内存)

为了防止这种情况,我建议子类化 JNA 的 Memory 以引入一个 AutoClosableMemory。这个想法是,使用 try-with-resource 构造将清楚地说明该资源在此范围内是强烈可达的。好处是我们可以在不再需要它时立即释放本机内存,而不必等待 GC(嘿,那是 RAII!)。

try (AutoClosableMemory m = [...]) {
  cApi.capture(m);
  cApi.test();
}

我能保证 m.close() 永远不会在 cApi.test() 之前被调用并且 m 是强可达的吗?在这种情况下,m 被底层 C API 捕获,但 java 编译器无法知道它。

对于一个线程,一切都保证 运行 与您的代码中的顺序完全相同(发生的任何重新排序都不会影响您的程序)。重新排序只会影响其他线程如何看待发生的事情。在你的问题中只有一个线程,所以你的程序保证在 m.close().

之前调用 cApi.test()

理论上,JVM 可以重新排序指令,但方法的结果必须保持正确。 HotSpot 不理解本机代码,为了保证正确性,它从不围绕本机指令重新排序代码。