當使用者希望把Feature Generator(FG)整合到Java服務中,則需要一個Java SDK去完成特徵的變換。本文介紹Feature Generator工具Java SDK的使用方法。
使用限制
Java SDK 目前只支援在
linux-x86_64和macosx-arm64平台上運行。
使用Java SDK
以Maven專案為例,介紹如何使用Java SDK。
1. 下載FG SDK的jar包到本地位置,如 /path/to/feature_generator-${version}-${platform}.jar。
platform | FG SDK Jar package |
linux-x86_64 | |
macosx-arm64 |
注意事項:目前FG預設採用std::hash做hash分箱,不同的平台上C++標準庫中std::hash的實現是不同的,因此最終的分箱結果是不一致的。
如果需要分箱結果的跨平台一致性,可以通過設定環境變數USE_FARM_HASH_TO_BUCKETIZE=true來實現,具體參考FG全域配置的文檔。
2. 使用mvn install:install-file安裝到本地倉庫
mvn install:install-file \
-Dfile=/path/to/feature_generator-${version}-${platform}.jar \
-DgroupId=com.aliyun.pai \
-DartifactId=feature_generator \
-Dversion=${version} \
-Dclassifier=${platform} \
-Dpackaging=jar注意:需要替換上述命令中的${version}為實際的版本號碼;${platform}要替換為實際的平台名稱。
3. 在你的pom.xml中添加依賴
<dependencies>
<dependency>
<groupId>com.aliyun.pai</groupId>
<artifactId>feature_generator</artifactId>
<version>${version}</version>
<classifier>linux-x86_64</classifier>
</dependency>
</dependencies>4. 建立fg.json檔案
Demo樣本:
{
"features": [
{
"feature_name": "query_word",
"feature_type": "id_feature",
"value_type": "String",
"expression": "user:query_word",
"default_value": "",
"combiner": "mean",
"need_prefix": false,
"is_multi": true
},
{
"feature_name": "query_match",
"feature_type": "lookup_feature",
"map": "user:query_token",
"key": "item:title",
"needDiscrete": false,
"needKey": false,
"default_value": "0",
"combiner": "sum",
"need_prefix": false,
"value_type": "double"
},
{
"feature_name": "goods_id",
"feature_type": "id_feature",
"value_type": "String",
"expression": "item:goods_id",
"default_value": "-1024",
"combiner": "mean",
"need_prefix": false,
"value_dimension": 1
},
{
"feature_name": "filter_type",
"feature_type": "id_feature",
"value_type": "String",
"expression": "item:filter_type",
"default_value": "-1024",
"combiner": "mean",
"need_prefix": false,
"value_dimension": 1
},
{
"feature_name": "day_h",
"feature_type": "id_feature",
"value_type": "int64",
"expression": "user:day_h",
"default_value": "0",
"combiner": "mean",
"need_prefix": false,
"value_dimension": 1
},
{
"feature_name": "week_day",
"feature_type": "id_feature",
"value_type": "int64",
"expression": "user:week_day",
"default_value": "0",
"combiner": "mean",
"need_prefix": false,
"value_dimension": 1
},
{
"feature_name": "city",
"feature_type": "id_feature",
"value_type": "String",
"expression": "user:city",
"default_value": "-1024",
"combiner": "mean",
"need_prefix": false,
"value_dimension": 1
},
{
"feature_name": "province",
"feature_type": "id_feature",
"value_type": "String",
"expression": "user:province",
"default_value": "-1024",
"combiner": "mean",
"need_prefix": false,
"value_dimension": 1
},
{
"feature_name": "country",
"feature_type": "id_feature",
"value_type": "String",
"expression": "user:country",
"default_value": "-1024",
"combiner": "mean",
"need_prefix": false,
"value_dimension": 1
},
{
"feature_name": "is_new_user",
"feature_type": "id_feature",
"value_type": "int64",
"expression": "user:is_new_user",
"default_value": "-1024",
"combiner": "mean",
"need_prefix": false,
"value_dimension": 1
},
{
"feature_name": "focus_author",
"feature_type": "id_feature",
"value_type": "String",
"expression": "user:focus_author",
"separator": ",",
"default_value": "-1024",
"combiner": "mean",
"need_prefix": false,
"is_multi": true
},
{
"feature_name": "title",
"feature_type": "id_feature",
"value_type": "String",
"expression": "item:title",
"default_value": "-1024",
"combiner": "mean",
"need_prefix": false,
"is_multi": true
}
],
"reserves": [
"request_id",
"user_id",
"is_click",
"is_pay"
]
}5. 參考如下代碼使用Java API
package org.example;
import com.aliyun.pai.fg.*;
import java.net.URL;
import java.util.*;
public class Main {
public static void main(String[] args) {
String filePath = "/path/to/fg.json";
FgHandler handler = new FgHandler(filePath, 4, false);
List<String> outputs = new ArrayList<>();
outputs.add("goods_id");
outputs.add("is_new_user");
outputs.add("day_h");
outputs.add("query_match"); // set output feature_name
outputs.add("title");
outputs.add("filter_type");
outputs.add("city");
outputs.add("province");
outputs.add("country");
outputs.add("focus_author");
handler.setOutputs(outputs.toArray(new String[0]));
List<String> expectGoods = Arrays.asList("218687106", "1142068348", "1142068347");
VariantVectorMap inputs = new VariantVectorMap.Builder()
.putOptionalString("goods_id", expectGoods)
.putOptionalInt32("is_new_user", Arrays.asList(0, 1, 0))
.putOptionalInt32("day_h", Arrays.asList(6, 8, 11))
.putListString("title", Arrays.asList(
Arrays.asList("k2", "k3", "k5"),
Arrays.asList("k1", "k2", "k3"),
Arrays.asList("k2", "k4")))
.putMapStringFloat("query_token", Arrays.asList(
new HashMap<String, Float>() {{
put("k2", 0.8f);
put("k3", 0.5f);
}},
new HashMap<String, Float>() {{
put("k1", 0.9f);
put("k4", 0.2f);
}},
new HashMap<String, Float>() {{
put("k1", 0.7f);
put("k2", 0.3f);
put("k4", 0.6f);
}}))
.putOptionalString("filter_type", Arrays.asList(null, "f1", null))
.putOptionalString("city", Arrays.asList("hangzhou"))
.putOptionalString("province", Arrays.asList("zhejiang"))
.putOptionalString("country", Arrays.asList("china"))
.putOptionalString("focus_author", Arrays.asList("2255010511022,14164467", "10511022,24164467", "550105110,34164467"))
.build();
VariantVectorMap results = handler.Process(inputs);
if (results == null || results.isNull()) {
System.out.println("fg result is null");
return;
}
inputs.close(); // 釋放native記憶體
System.out.println("result size=" + results.size());
List<String> features = results.getKeys();
System.out.println("result features=" + features);
List<String> goodsIds = results.getList("goods_id");
System.out.println("goods_ids=" + String.join(", ", goodsIds));
List<List<String>> titles = results.getList("title");
System.out.println("titles=" + titles);
List<Long> dayHours = results.getList("day_h");
System.out.println("day_h=" + dayHours);
List<String> filters = results.getList("filter_type");
System.out.println("filter_type=" + String.join(", ", filters));
List<List<String>> focus = results.getList("focus_author");
System.out.println("focus_author=" + focus);
List<String> citys = results.getList("city");
System.out.println("city=" + String.join(", ", citys));
List<String> provinces = results.getList("province");
System.out.println("provinces=" + String.join(", ", provinces));
List<String> countrys = results.getList("country");
System.out.println("country=" + String.join(", ", countrys));
List<Long> isNewUsers = results.getList("is_new_user");
System.out.println("is_new_user=" + isNewUsers);
List<Double> queryMatch = results.getList("query_match");
System.out.println("query_match=" + queryMatch);
System.out.println("===========================================================");
Set<String> itemInputs = handler.GetItemInputNames();
System.out.println("item side inputs=" + itemInputs);
Set<String> userInputs = handler.GetUserInputNames();
System.out.println("user side inputs=" + userInputs);
Set<String> ctxInputs = handler.GetContextInputNames();
System.out.println("context side inputs=" + ctxInputs);
Set<String> reserved = handler.GetReserveColumns();
System.out.println("reserved columns =" + reserved);
Set<String> qminputs = handler.GetFeatureInputs("query_match");
System.out.println("inputs of query_match =" + qminputs);
String defaultVal = handler.DefaultFeatureValue("query_match");
System.out.println("default feature value of query_match =" + defaultVal);
List<String> allFeatures = handler.GetAllFeatureNames();
System.out.println("all feature names:" + allFeatures);
Map<String, String> schema = handler.GetTableSchema();
System.out.println("table schema:" + schema);
results.close(); // 釋放native記憶體
handler.close(); // 釋放資源
}
}Java SDK 使用文檔
1. 類概述
FgHandler 用於根據配置(config_json)對輸入特徵進行處理,並輸出處理後的特徵結果。
主要能力:
通過配置初始化特徵處理器(可指定線程數、是否只做 bucketize 等)。
處理輸入特徵:Process(VariantVectorMap inputs) -> VariantVectorMap
設定輸出特徵集合:setOutputs(...)
查詢配置中的輸入/輸出/特徵元資訊(feature names、schema、預設值、維度等)。
支援顯式釋放 native 資源:close()(非常重要)
2. 快速開始
2.1 初始化
提供四種構造方式(最終都會調用 native allocate):
// 1) 僅配置
FgHandler handler = new FgHandler(configJsonOrPath);
// 2) 配置 + 線程數
FgHandler handler = new FgHandler(configJsonOrPath, threadNum);
// 3) 配置 + 線程數 + 是否僅 bucketize
FgHandler handler = new FgHandler(configJsonOrPath, threadNum, bucketizeOnly);
// 4) 配置 + 線程數 + 是否僅 bucketize + config 是否為檔案路徑
FgHandler handler = new FgHandler(configJsonOrPath, threadNum, bucketizeOnly, isCfgPath);參數語義:
config_json:配置內容或配置路徑(由 is_cfg_path 決定)
thread_num:native 內部處理線程數(size_t)
bucketize_only:是否只執行 bucketize(可通過 IsOnlyBucketize() 查詢)
is_cfg_path:true 表示 config_json 參數是設定檔路徑;false 表示配置 JSON 文本
2.2 設定輸出特徵
可通過兩種方式設定輸出:
handler.setOutputs("feature_a", "feature_b");
// 或者使用 String[] 數組作為參數
List<String> outputs = new ArrayList<>();
outputs.add("goods_id");
outputs.add("is_new_user");
outputs.add("title");
outputs.add("city");
outputs.add("province");
outputs.add("country");
handler.setOutputs(outputs.toArray(new String[0]));或使用底層介面:
StrVector outs = new StrVector();
outs.push_back("feature_a");
outs.push_back("feature_b");
handler.setOutputs(outs);注意:如果不設定輸出,則預設輸出配置的所有特徵。
2.3 執行處理
VariantVectorMap inputs = new VariantVectorMap();
// TODO: 按你的 VariantVectorMap 約定填充 inputs
VariantVectorMap outputs = handler.Process(inputs);
if (outputs == null || outputs.isNull()) {
System.out.println("fg output is null");
inputs.close();
return;
}
// TODO: 從 outputs 讀取結果FgHandler.Process(inputs) 一次處理一批樣本(batch),其輸入輸出類型都是 VariantVectorMap,含義是:
VariantVectorMap:Map<String, VariantVector>(key 為欄位/特徵名)
VariantVector:一個“按列儲存”的批資料容器(列向量),支援多種嵌套形態:
Optional 標量:List<T>(長度 = batch size;可包含 null)
List 序列:List<List<T>>(外層長度 = batch size)
Map 特徵:List<Map<K,V>>(外層長度 = batch size)
Matrix 特徵:List<List<List<T>>>(外層長度 = batch size,每個樣本一個二維矩陣)
Process函數的返回結果可能為null,表示有執行失敗的特徵變換,使用之前需要檢查是否為空白。
3. 輸入與輸出資料結構說明
3.1 VariantVectorMap(輸入/輸出容器)
3.1.1 基本 API
構造:
new VariantVectorMap()
VariantVectorMap.fromJavaMap(Map<String, VariantVector>)
new VariantVectorMap.Builder()...build()
寫入:
put(String key, VariantVector value)
putAll(Map<String, VariantVector>)
讀取:
VariantVector get(String key)
boolean contains(String key)
List<String> getKeys()
long size()
Map<String, VariantVector> toJavaMap()(把當前 map 中的索引值轉成 Java Map)
釋放:
clear() 清空元素
close() 釋放native記憶體,必須調用,否則會有記憶體流失問題
3.1.2 推薦:Builder 構造 inputs
VariantVectorMap.Builder 提供大量 putXXX 方法:
常見(標量 optional,對應一個batch):
VariantVectorMap inputs = new VariantVectorMap.Builder()
.putOptionalInt64("user_id", Arrays.asList(1001L, 1002L))
.putOptionalString("item_id", Arrays.asList("i1", "i2"))
.putOptionalDouble("score", Arrays.asList(0.1, null)) // 支援 null
.build();單值便捷(batch=1):
VariantVectorMap inputs = new VariantVectorMap.Builder()
.putSingleOptionalInt64("user_id", 1001L)
.putSingleOptionalString("item_id", "i1")
.build();序列(List 類型,batch 內每個樣本一個 list):
VariantVectorMap inputs = new VariantVectorMap.Builder()
.putListInt64("clicked_item_ids",
Arrays.asList(
Arrays.asList(11L, 12L),
Arrays.asList(21L)
)
)
.build();Map 特徵(batch 內每個樣本一個 map):
VariantVectorMap inputs = new VariantVectorMap.Builder()
.putMapStringFloat("user_dense",
Arrays.asList(
Map.of("age", 18.0f, "lvl", 3.0f),
Map.of("age", 25.0f)
)
)
.build();Matrix(每個樣本一個二維矩陣):
VariantVectorMap inputs = new VariantVectorMap.Builder()
.putMatrixFloat("image_emb",
Arrays.asList(
Arrays.asList( // sample1 matrix
Arrays.asList(0.1f, 0.2f),
Arrays.asList(0.3f, 0.4f)
),
Arrays.asList( // sample2 matrix
Arrays.asList(1.0f, 2.0f)
)
)
)
.build();3.1.3 getList:按型別安全方式讀取(輸出側常用)
VariantVectorMap.getList(key) 會根據 VariantVector.getType() 自動選擇註冊的 TypeReference 做轉換。
樣本:讀取輸出 feature_x(假設是 OPTIONAL_FLOAT):
List<Float> xs = out.getList("feature_x"); // 實際返回 List<Float>或讀取 LIST_INT64(返回 List<List>):
List<List<Long>> seq = out.getList("seq_feature");注意:
getList 傳回型別是 <T> List<T>,編譯期無法強約束;類型不符會在運行時拋 IllegalArgumentException("Type mismatch...")。
TYPE_REGISTRY 已預註冊了所有你定義的類型常量到 TypeReference 的映射。
建議做法:先擷取輸出表的Schema資訊,再根據schema決定getList函數的傳回值類型。
Map<String, String> schema = handler.GetTableSchema();
System.out.println("table schema:" + schema);3.2 VariantVector(列向量與類型系統)
VariantVector 是“單列”資料,必須設定 type 並按該類型組織內部資料。
3.2.1 支援類型一覽(按常量)
Optional(允許 null,外層即 batch):
OPTIONAL_STRING / INT32 / INT64 / FLOAT / DOUBLE
Java 表示:List<T>(長度 = batch;元素可為 null)
List(每條樣本一段序列):
LIST_STRING / INT32 / INT64 / FLOAT / DOUBLE
Java 表示:List<List<T>>(外層長度 = batch)
Map(每條樣本一個 map):
MAP_STRING_*:List<Map<String, V>>
MAP_INT32_*:List<Map<Integer, V>>
MAP_INT64_*:List<Map<Long, V>>
Matrix(每條樣本一個 2D 矩陣):
MATRIX_FLOAT / MATRIX_INT64 / MATRIX_STRING
Java 表示:List<List<List<T>>>
3.2.2 推薦構造方式:靜態方法 fromXXX
你通常不需要手寫底層 Builder(type).withXXXData(),直接用:
VariantVector.fromOptionalInt64(List<Long> values)
VariantVector.fromListFloat(List<List<Float>> values)
VariantVector.fromMapStringInt32(List<Map<String,Integer>> maps)
VariantVector.fromMatrixFloat(List<List<List<Float>>> matrices) 等。
樣本(手動構造再放入 map):
VariantVector userIds = VariantVector.fromOptionalInt64(Arrays.asList(1L, 2L, null));
VariantVectorMap inputs = new VariantVectorMap();
inputs.put("user_id", userIds);通過這種方式構建的對象擁有自己的 native 記憶體,使用完之後需要調用close()釋放native記憶體。
還有一種借用記憶體(borrowed)模式的對象,比如:
VariantVectorMap results;
// build results
VariantVector v = results.get("goods_id");這種類型的對象,本質上是一種視圖,底層的記憶體與VariantVectorMap對象共用,當VariantVectorMap對象被釋放時,該對象就變成“懸空”狀態,不能夠再被使用。
borrowed 類型的VariantVector對象,使用完之後不需要調用close(),注意不要單獨長期儲存,生命週期依賴所屬的VariantVectorMap對象。
3.2.3 讀取:toXXXList
拿到一個 VariantVector vv 後,可按類型調用轉換方法:
VariantVector vv = out.get("feature_x");
if (vv.getType() == VariantVector.OPTIONAL_DOUBLE) {
List<Double> vals = vv.toOptionalDoubleList();
}也可用 VariantVectorMap.getList(key) 自動分發(見上文 3.1.3)。
3.2.4 輔助方法:size / isEmpty / totalElementCount
vv.size():
Optional:返回 batch size(nullFlags 長度)
其他:返回 sizes[0](外層元素數,通常=batch size)
vv.isEmpty():根據 type 檢查底層資料是否為空白
vv.totalElementCount():對 list/map/matrix 計算嵌套展開後的總元素量(主要用於調試/檢查)
4. 元資訊查詢介面
這些介面主要用來瞭解配置內容與特徵依賴。
4.1 查詢輸入欄位集合
Set<String> itemInputs = handler.GetItemInputNames();
Set<String> userInputs = handler.GetUserInputNames();
Set<String> ctxInputs = handler.GetContextInputNames();4.2 查詢所有特徵名 / 保留列 / 特殊集合
List<String> allFeatures = handler.GetAllFeatureNames();
Set<String> reserveCols = handler.GetReserveColumns();
Set<String> userSide = handler.GetUserSideFeatures();
Set<String> seqFeatures = handler.GetSequenceFeatures();4.3 查詢特徵依賴與預設值/維度/bucketizer
Set<String> deps = handler.GetFeatureInputs("feature_x");
String defaultVal = handler.DefaultFeatureValue("feature_x");
long defaultBucket = handler.DefaultBucketizedFeatureValue("feature_x");
long dim = handler.GetFeatureValueDim("feature_x");
boolean hasBucketizer = handler.HasBucketizer("feature_x");
boolean onlyBucketize = handler.IsOnlyBucketize();適用情境:
產生輸入準備邏輯(知道某個 feature 需要哪些 raw inputs)
輸入缺失時進行補齊/校正(預設值)
下遊模型需要固定維度時(dim)
決定是否需要額外 bucketize 相關處理(hasBucketizer/onlyBucketize)
4.4 查詢MC輸出表結構(Schema)
使用方式:
Map<String, String> schema = handler.GetTableSchema();
String type = schema.get("feature_x");5. 資源釋放(必須閱讀)
FgHandler 持有 native 側對象指標,必須在不用時釋放,否則可能造成記憶體泄露。
這是個重量級對象,一般:
初始化一次
重複復用
服務退出時再 close()
正確姿勢
不要每個請求都 new FgHandler()
一個配置對應一個長期複用的 handler
5.1 當FgHandler不再被使用時,必須調用close()函數釋放資源。該函數有如下特性:
安全執行緒:synchronized
等冪:重複調用不會重複釋放
重要:close() 後不要再調用任何 native 方法(包括 Process、查詢介面等),否則可能崩潰(use-after-free)。
FgHandler handler = new FgHandler(cfg, 4);
try {
...
} finally {
handler.close();
}5.2 調試階段可以用try-with-resources
try (FgHandler handler = new FgHandler(cfg, 4)) {
handler.setOutputs("f1", "f2");
VariantVectorMap out = handler.Process(in);
}5.3 正式生產環境不要頻繁建立相同的FgHandler對象
重要:在一個服務內部使用FgHandler時,不要每次請求都建立FgHandler對象,因為該對象的初始化開銷比較大,頻繁建立會影響服務效能。
推薦在服務的初始化函數裡建立全域對象,在服務的退出函數裡調用close()函數;需要建立多個不同的FgHandler對象時,也要遵循同樣的模式。
最推薦的請求處理模式
模式 A:同步請求,最穩
public Result handle(Request req, FgHandler handler) {
try (VariantVectorMap inputs = buildInputs(req);
VariantVectorMap results = handler.Process(inputs)) {
Result r = new Result();
r.goodsIds = results.get("goods_id").toOptionalStringList();
r.dayHs = results.get("day_h").toOptionalInt32List();
r.titles = results.get("title").toListStringList();
return r;
}
}特點:
native 資源範圍非常清楚
請求結束即釋放
不容易 OOM
模式 B:服務級單例 handler
public class FgService implements AutoCloseable {
private final FgHandler handler;
public FgService(String configPath) {
this.handler = new FgHandler(configPath, 4, false, true);
}
public Result process(Request req) {
try (VariantVectorMap inputs = buildInputs(req);
VariantVectorMap results = handler.Process(inputs)) {
return decode(results);
}
}
@Override
public void close() {
handler.close();
}
}這是生產裡最推薦的方式。
6. 常見用法樣本
處理前先根據配置準備輸入欄位
FgHandler handler = new FgHandler(cfgJson, 8, false, false);
try {
// 1) 設定需要的輸出
handler.setOutputs("feature_a", "feature_b");
// 2) 瞭解需要準備哪些輸入
Set<String> itemInputs = handler.GetItemInputNames();
Set<String> userInputs = handler.GetUserInputNames();
Set<String> ctxInputs = handler.GetContextInputNames();
// 3) 構造 inputs(示意,具體取決於 VariantVectorMap 的 API)
VariantVectorMap inputs = new VariantVectorMap();
// inputs.put("user_id", ...)
// inputs.put("item_id", ...)
// inputs.put("ts", ...)
VariantVectorMap outputs = handler.Process(inputs);
inputs.close();
// 讀取 outputs...
outputs.close();
} finally {
handler.close();
}針對只做 bucketize
FgHandler handler = new FgHandler(cfgJson, 4, true, false);
try {
if (!handler.IsOnlyBucketize()) {
// 配置/構造參數不匹配時可做提示
}
...
} finally {
handler.close();
}安全的標準用法
try (FgHandler handler = new FgHandler(configPath, 4, false, true)) {
handler.setOutputs("goods_id", "day_h", "title");
try (VariantVectorMap inputs = new VariantVectorMap.Builder()
.putOptionalString("goods_id", Arrays.asList("1", "2"))
.putOptionalInt32("day_h", Arrays.asList(6, 8))
.putListString("title", Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")))
.build();
VariantVectorMap results = handler.Process(inputs)) {
List<String> goodsIds = results.get("goods_id").toOptionalStringList();
List<Integer> dayHs = results.get("day_h").toOptionalInt32List();
List<List<String>> titles = results.get("title").toListStringList();
// 用完即止,不把 results / borrowed VariantVector 長時間帶出範圍
}
}7. 哪些用法最容易導致 OOM
1. 沒有 close VariantVectorMap
錯誤樣本
for (...) {
VariantVectorMap inputs = new VariantVectorMap.Builder()...build();
VariantVectorMap results = handler.Process(inputs);
// 沒有 close
}這會導致:
Java 對象可能很快變成垃圾,被GC回收
但 native 記憶體不一定及時釋放
高 QPS 下很容易導致分配了大量的對象但是沒有被釋放,最終 OOM 或 native RSS 爆掉
正確樣本
for (...) {
try (VariantVectorMap inputs = new VariantVectorMap.Builder()...build();
VariantVectorMap results = handler.Process(inputs)) {
...
}
}在 try-with-resources 模式下會自動釋放資源。
2. 每個請求都 new 一個 FgHandler
錯誤樣本
for (...) {
FgHandler handler = new FgHandler(configPath, 4, false, true);
...
}這通常非常重:
反覆載入配置
初始化線程池/內部結構
native 資源重複申請
正確樣本
FgHandler handler = new FgHandler(configPath, 4, false, true);
try {
for (...) {
...
}
} finally {
handler.close();
}3. 緩衝 results.get(key) 傳回值
錯誤樣本
VariantVector v = results.get("goods_id");
cache.add(v); // 錯因為它是 borrowed:
一旦
results.close(),這些對象就失效如果你不 close
results,那又會導致記憶體一直不釋放
正確樣本
要麼立刻解碼成普通 Java 對象:
List<String> goodsIds = results.get("goods_id").toOptionalStringList();
cache.add(goodsIds);要麼如果確實要儲存 native 對象,用 getCopy(),並負責後續 close:
VariantVector copy = results.getCopy("goods_id");
try {
...
} finally {
copy.close();
}但一般不建議緩衝 native copy,除非確實必要。
4. 大量使用 toJavaMap() 後不釋放 value
如果你的 toJavaMap() 現在是返回 owned copy ,那這很容易製造很多 native 對象。
錯誤樣本
Map<String, VariantVector> map = results.toJavaMap();
// 放著不關正確樣本
除非真有必要,不要這麼幹。
更推薦直接按欄位讀取成 Java 對象。
如果必須這樣做:
Map<String, VariantVector> map = results.toJavaMap();
try {
...
} finally {
for (VariantVector v : map.values()) {
if (v != null) v.close();
}
}5. Builder 產生的中間 VariantVector 長期留在 Java Map 中
例如:
VariantVectorMap.Builder builder = new VariantVectorMap.Builder();
builder.putOptionalString(...);
builder.putOptionalInt32(...);
// builder/data 長期存在Builder 內部持有很多 VariantVector,這些都是 owned native 對象。
如果 Builder 長期不釋放,也會佔 native 記憶體。
正確姿勢
Builder 的生命週期盡量短,只用來構建一次請求。