You need to enable JavaScript to run this app. 如何通过JNA将Java字符串数组传递给cgo编译的Go函数并排查段错误
优惠活动
大模型
产品
解决方案
定价
更多

如何通过JNA将Java字符串数组传递给cgo编译的Go函数并排查段错误

问题成因

你遇到的段错误是由多个问题共同导致的,按影响优先级排序如下:

  1. 数组写入逻辑错误:JavaStringArrayToGoStringSlice方法的循环中,你固定读取goStrings[0]的结构体数据写入所有数组位置,导致除第一个元素外的所有Go string结构体都是错误值,Go侧访问时直接指向非法地址。
  2. JNA结构体内存未同步:_GoString_.ByValue是JNA的Structure对象,你设置完p、n字段后没有调用write()方法将Java侧的字段值同步到native内存,直接读取getPointer()拿到的是未初始化的垃圾数据,错误地址0xb01dfacedebac1e就是JVM回收内存后填充的毒指针,明确说明访问的内存已经失效。
  3. native内存无强引用持有:你在转换过程中创建的DirectByteBuffer、Memory对象没有被任何强引用持有,方法返回后会被JVM GC直接回收,Go侧访问时对应的内存已经被释放。
  4. 字段类型不匹配(潜在问题):_GoString_结构的长度字段你用了NativeSize,而Go string的长度字段类型是GoInt(64位系统下为int64),虽然64位环境下两者宽度一致,但32位环境下会直接出错。
可行解决方案

你可以按照以下步骤修正代码,完全规避问题:

1. 修正_GoString_结构定义

将长度字段类型替换为long,和Go侧类型完全对齐:

public class _GoString_ extends Structure {
    /** C type : const char* */
    public Pointer p;
    // 替换NativeSize为long,对齐GoInt类型
    public long n;
    public _GoString_() {
        super();
    }
    protected List<String> getFieldOrder() {
        return Arrays.asList("p", "n");
    }
    public _GoString_(Pointer p, long n) {
        super();
        this.p = p;
        this.n = n;
    }
    public static class ByReference extends _GoString_ implements Structure.ByReference {};
    public static class ByValue extends _GoString_ implements Structure.ByValue {};
}

2. 增加native内存持有逻辑,避免GC回收

使用ThreadLocal临时持有本次调用涉及的所有native内存对象,调用完Go函数后手动清理:

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

// 临时持有native内存引用,避免GC提前回收
private static final ThreadLocal<List<Object>> NATIVE_MEM_HOLDER = ThreadLocal.withInitial(ArrayList::new);

3. 修正字符串转换逻辑

移除Unsafe相关的不安全操作,用JNA自带的Memory存储字符串字节,同步结构体到native内存:

public static _GoString_.ByValue JavaStringToGo(String jstr) {
    byte[] bytes = jstr.getBytes(StandardCharsets.UTF_8);
    // 申请native内存存储字符串字节
    Memory strMem = new Memory(bytes.length);
    strMem.write(0, bytes, 0, bytes.length);
    NATIVE_MEM_HOLDER.get().add(strMem);
        
    _GoString_.ByValue goStr = new _GoString_.ByValue();
    goStr.p = strMem;
    goStr.n = bytes.length;
    // 同步Java字段到native内存
    goStr.write();
    NATIVE_MEM_HOLDER.get().add(goStr);
    return goStr;
}

4. 修正字符串数组转换逻辑

修复循环索引错误,正确写入每个Go string结构体:

public static GoSlice.ByValue JavaStringArrayToGoStringSlice(String[] strings) {
    int strStructSize = Native.getNativeSize(_GoString_.class);
    _GoString_.ByValue[] goStrings = new _GoString_.ByValue[strings.length];
    for (int i = 0; i < strings.length; i++) {
        goStrings[i] = JavaStringToGo(strings[i]);
    }

    // 申请内存存储Go string结构体数组
    Memory arrMem = new Memory((long) strings.length * strStructSize);
    NATIVE_MEM_HOLDER.get().add(arrMem);
    for (int i = 0; i < strings.length; i++) {
        // 修正索引,读取当前i对应的结构体数据
        Pointer structPtr = goStrings[i].getPointer();
        byte[] structBytes = structPtr.getByteArray(0, strStructSize);
        arrMem.write((long) i * strStructSize, structBytes, 0, structBytes.length);
    }

    GoSlice.ByValue slice = new GoSlice.ByValue();
    slice.data = arrMem;
    slice.len = strings.length;
    slice.cap = strings.length;
    slice.write();
    NATIVE_MEM_HOLDER.get().add(slice);
    return slice;
}

// 调用完Go函数后必须调用此方法清理引用,避免内存泄漏
public static void clearNativeMem() {
    NATIVE_MEM_HOLDER.get().clear();
}

5. 正确调用Go函数的示例

// 1. 转换参数
GoSlice.ByValue keys = JavaStringArrayToGoStringSlice(new String[]{"key1", "key2"});
GoSlice.ByValue values = JavaStringArrayToGoStringSlice(new String[]{"val1", "val2"});
_GoString_.ByValue expr = JavaStringToGo("your_expression");

// 2. 调用Go函数(请替换为你自己的JNA接口实例)
Pointer resultPtr = YourGoLib.INSTANCE.PredicateEval(keys, values, expr);

// 3. 读取返回结果
String result = resultPtr.getString(0, StandardCharsets.UTF_8.name());

// 4. 释放内存:Go返回的*C.char需要手动释放(你Go侧应该是用C.CString创建的返回值)
Native.free(resultPtr);
// 清理Java侧持有的native内存引用,允许GC回收
clearNativeMem();

内容的提问来源于stack exchange,提问作者Tian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 01:30:03