Java通过JNA调用Delphi DLL多次后出现无效内存访问错误排查
问题分析:Java通过JNA调用Delphi DLL出现"Invalid memory access"错误
Java通过JNA调用自行开发的Delphi DLL时,前几次调用正常,但多次调用后抛出Invalid memory access错误。仅在将结果转为PAnsiChar返回给Java时触发该问题,直接将结果写入文件则无异常。
相关代码信息
Java接口签名
public interface DLL extends Library { DLL CCAWDll = Native.load("invoke", DLL.class); String invoke(String fileName, float a, float b, float c, float d, float e, float f, float g, float h, float i, float j, String k); }
Delphi函数签名
function invoke(filename: PAnsiChar; a, b, c, d, e, f, g, h, i, j: single; k: PAnsiChar): PAnsiChar; stdcall;
错误栈信息
===================================9th time, call DLL=================================== ===================================10th time, call DLLL=================================== 2023-03-10 10:25:56.417 ERROR 12592 --- [-nio-94-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet] : > Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.Error: Invalid memory access] with root cause java.lang.Error: Invalid memory access at com.sun.jna.Native.invokePointer(Native Method) ~[jna-5.5.0.jar:5.5.0 (b0)] at com.sun.jna.Function.invokePointer(Function.java:497) ~[jna-5.5.0.jar:5.5.0 (b0)] at com.sun.jna.Function.invokeString(Function.java:660) ~[jna-5.5.0.jar:5.5.0 (b0)] at com.sun.jna.Function.invoke(Function.java:434) ~[jna-5.5.0.jar:5.5.0 (b0)] at com.sun.jna.Function.invoke(Function.java:361) ~[jna-5.5.0.jar:5.5.0 (b0)] at com.sun.jna.Library$Handler.invoke(Library.java:265) ~[jna-5.5.0.jar:5.5.0 (b0)] at com.sun.proxy.$Proxy98.invoke(Unknown Source) ~[na:na] at com.xxx.xxx.dll.impl.DllImpl.invoke(DllImpl.java:40) ~[classes/:na] at com.xxx.xxx.dll.impl.DllImpl$$FastClassBySpringCGLIB$$92dce636.invoke(<generated>) ~[classes/:na] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.12.RELEASE.jar:5.1.12.RELEASE] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750) ~[spring-aop-5.1.12.RELEASE.jar:5.1.12.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.1.12.RELEASE.jar:5.1.12.RELEASE] at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:120) ~[spring-context-5.1.12.RELEASE.jar:5.1.12.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.12.RELEASE.jar:5.1.12.RELEASE] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.1.12.RELEASE.jar:5.1.12.RELEASE] at com.xxx.xxx.dll.impl.DllImpl$$EnhancerBySpringCGLIB$$c67e343.invoke(<generated>) ~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_341] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_341] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_341] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_341]
Java调用代码
public class DllImpl extends DllManage implements DllProvider { public static AtomicInteger count = new AtomicInteger(0); @Override public synchronized ResponseEntity<String> invoke(String fileName, float a, float b, float c, float d, float e, float f, float g, float h, float i, float j, String k) { System.setProperty("jna.encoding", "GBK"); count.getAndIncrement(); String result = null; System.out.println("==================================="+count+"th time, call DLL==================================="); try { result = DLL.CCAWDll.invoke(fileName, a,b,c,d,e,f,g,h,i,j, k); } catch (Exception ex) { ex.printStackTrace(); } System.gc(); return new ResponseEntity<>(result, HttpStatus.OK); } }
Delphi核心逻辑
QueryPerformanceCounter(t2); // Get end count value r1 := (t2 - t1) / c1; // Get the timing time in seconds (s) r2 := (t2 - t1) / c1 * 1000; // Get the timing time in milliseconds (ms) r3 := (t2 - t1) / c1 * 1000000; // Get the timing time in microseconds WritelnTxt('fileName:' + filename); WritelnTxt('Time of calling CCAWDll:' + FloatToStr(r2)); //TJsonSerializer class, more efficient and faster serial := TJsonSerializer.Create; S := serial.Serialize<INSOLUTION>(returnDetail); // [{"name": "Zhang Zhang", "age": 1}, {"name": "Wang Wang", "age": 2}] QueryPerformanceCounter(t3); // Get end count value r4 := (t3 - t2) / c1 * 1000; // Get timing time WritelnTxt('Generate json time:' + FloatToStr(r4)); //write file WritelnTxtFile(S); //return GetMem(PStr, Length(S) + 1); // This 1 is used to store the 0 string terminator StrPCopy(PStr, S); Result := PAnsiChar(PStr);
问题结论
问题根源是DLL内部内存管理缺陷,参数类型匹配无问题:
- 参数类型验证:Java的
float对应Delphi的single,String对应PAnsiChar,调用约定为stdcall,参数数量、顺序完全匹配,不存在类型不匹配问题。 - 内存泄露引发非法访问:Delphi通过
GetMem分配内存并返回给Java,但没有释放机制。每次调用都会占用新内存,多次调用后内存耗尽,后续GetMem可能分配到无效区域,或JNA读取时访问已被系统回收的内存,触发Invalid memory access。直接写入文件无需分配返回内存,因此无异常。 - 佐证:固定第10次调用出错符合内存逐步耗尽的特征;
System.gc()无法解决,因为Java无法管理Delphi分配的本地内存。
解决方案
方案一:DLL提供内存释放函数
Delphi新增释放函数:
procedure FreeMemStr(P: PAnsiChar); stdcall; begin if P <> nil then FreeMem(P); end;
Java接口新增对应方法并调用:
void FreeMemStr(String str); // 调用后释放内存 try { result = DLL.CCAWDll.invoke(...); // 处理结果 } finally { if (result != null) { DLL.CCAWDll.FreeMemStr(result); } }
方案二:Java分配内存传入DLL
由Java提前分配足够的内存缓冲区,DLL直接写入该缓冲区,避免跨边界内存管理问题。
方案三:使用共享内存管理器(不推荐)
使用Delphi的SharedMem单元,让内存分配和释放使用共享内存管理器,但兼容性较差,仅适合特定场景。
内容的提问来源于stack exchange,提问作者JnoZhen
相关产品推荐
相关产品推荐

