Delphi中REST与JSON类的内存释放问题:是否需手动释放?
Delphi Xero令牌刷新过程的内存管理问题解析
1. 释放jObject触发访问违规的原因
你的代码存在双重释放问题:
- 先执行
jObject := TJSONObject.Create;创建了独立的TJSONObject对象,但紧接着jObject := xeroResponse.JSONValue as TJSONObject;将变量指向xeroResponse内部管理的JSON对象,导致之前创建的对象丢失(内存泄漏)。 xeroResponse.JSONValue返回的对象所有权属于xeroResponse,手动jObject.Free后,xeroResponse自身Free时会再次尝试销毁已被释放的对象,直接触发访问违规。
2. 内存泄漏与对象释放规则
REST组件(TRESTClient/TRESTRequest/TRESTResponse)
你通过Create(nil)手动创建这些对象(未指定Owner),因此必须手动调用Free释放,当前代码的释放逻辑是正确的。释放顺序建议遵循「反向创建顺序」:先释放xeroRequest,再xeroResponse,最后xeroClient——因为xeroRequest依赖xeroClient和xeroResponse,反向释放能避免潜在引用问题,即使顺序不对也大概率不会出错,但这是更规范的做法。
JSON对象(TJSONValue/TJSONArray/TJSONObject)
- 手动创建的对象:如果是你通过
TJSONObject.Create/TJSONArray.Create创建的对象,必须手动Free,否则会造成内存泄漏。 - 从RESTResponse获取的对象:
xeroResponse.JSONValue返回的对象由xeroResponse负责生命周期管理,无需手动释放,否则会导致双重释放。 - JSON子节点:通过
GetValue获取的TJSONValue子节点(比如jObject.GetValue('access_token'))是父JSON对象的一部分,所有权属于父对象,不需要单独释放,父对象销毁时会自动清理所有子节点。
3. 代码修复与优化
以下是修复后的代码,解决了内存泄漏和访问违规问题:
procedure CheckXeroTokenValidity; var xeroClient: TRESTClient; xeroRequest: TRESTRequest; xeroResponse: TRESTResponse; jObject : TJSONObject; sRefreshToken : string; begin // 检查访问令牌是否超过30分钟,若刷新令牌未超过60天则刷新 with dmProcs do begin ResetTable(tXero, '', True); tXero.First; if not tXero.Eof then begin // 令牌即将过期(超过25分钟) if MinutesBetween(tXeroDateToken.AsDateTime, Now) > 25 then begin // 刷新令牌仍有效(小于59天) if DaysBetween(tXeroDateToken.AsDateTime, Date) < 59 then begin sRefreshToken := tXeroRefreshToken.AsString; xeroClient := TRESTClient.Create(nil); xeroRequest := TRESTRequest.Create(nil); xeroResponse := TRESTResponse.Create(nil); Screen.Cursor := crHourGlass; try xeroClient.ContentType := 'application/json'; xeroResponse.ContentType := 'application/json'; xeroRequest.Client := xeroClient; xeroRequest.Response := xeroResponse; xeroRequest.Method := TRESTRequestMethod.rmPOST; xeroRequest.Params.Clear; xeroRequest.Params.AddItem('grant_type', 'refresh_token'); xeroRequest.Params.AddItem('client_id', sClientID); xeroRequest.Params.AddItem('refresh_token', sRefreshToken); xeroClient.BaseURL := 'https://identity.xero.com/connect/token'; try xeroRequest.Execute; if Assigned(xeroResponse.JSONValue) then begin // 直接转换RESTResponse的JSON对象,无需手动创建 jObject := xeroResponse.JSONValue as TJSONObject; if Assigned(jObject) and (xeroResponse.StatusCode = 200) and Assigned(jObject.GetValue('access_token')) then begin try tXero.Edit; tXeroAccessToken.AsString := jObject.GetValue('access_token').Value; tXeroRefreshToken.AsString := jObject.GetValue('refresh_token').Value; tXeroDateToken.AsDateTime := Now; tXero.Post; except on E:Exception do ShowMessage('Error: ' + E.Message); end; end else begin // 出错或未返回有效令牌,删除Xero记录 tXero.First; if not tXero.Eof then begin tXero.Delete; end; if Assigned(jObject) and Assigned(jObject.GetValue('error')) then begin ShowMessage('Error: ' + jObject.GetValue('error').ToString); end; end; end; except on E:Exception do ShowMessage('Error: ' + xeroResponse.ErrorMessage + ' - ' + E.Message); end; finally Screen.Cursor := crDefault; // 按反向创建顺序释放REST组件 xeroRequest.Free; xeroResponse.Free; xeroClient.Free; // 无需释放jObject,其所有权属于xeroResponse end; end else begin Information('请联系管理员重新建立会计系统连接。'); end; end; end; end; end;
修复点说明:
- 移除
jObject := TJSONObject.Create;,避免内存泄漏; - 增加
Assigned(jObject)判断,防止类型转换失败导致空指针; - 调整REST组件释放顺序,符合规范;
- 明确不手动释放
jObject,交由xeroResponse管理。
内容的提问来源于stack exchange,提问作者Paul Coshott
相关产品推荐
相关产品推荐

