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 不理解本机代码,为了保证正确性,它从不围绕本机指令重新排序代码。
给定以下代码示例:
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 不理解本机代码,为了保证正确性,它从不围绕本机指令重新排序代码。