自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何低開(kāi)銷(xiāo)的監(jiān)控JVM對(duì)象分配及分配對(duì)象的線程

開(kāi)發(fā) 前端
JTreg框架中針對(duì)該特性有16個(gè)測(cè)試:使用多個(gè)線程打開(kāi)/關(guān)閉,同時(shí)分配多個(gè)線程,測(cè)試數(shù)據(jù)是否以正確的間隔采樣,以及收集的堆棧是否反映正確的程序信息。

概要

提供一種低開(kāi)銷(xiāo)的Java堆分配采樣方式,可通過(guò)JVMTI訪問(wèn)。

目標(biāo)

提供一種從JVM獲取Java對(duì)象堆分配信息的方法:

  • 開(kāi)銷(xiāo)足夠低,可以在默認(rèn)情況下連續(xù)啟用,
  • 可以通過(guò)定義良好的編程接口訪問(wèn),
  • 可以對(duì)所有的分配進(jìn)行抽樣(即,不局限于一個(gè)特定堆區(qū)域中的分配或以一種特定方式分配的分配),
  • 可以以一種與實(shí)現(xiàn)無(wú)關(guān)的方式定義(即,不依賴(lài)于任何特定的GC算法或VM實(shí)現(xiàn)),以及
  • 可以提供有關(guān)活的和死的Java對(duì)象的信息。

動(dòng)機(jī)

用戶(hù)非常需要理解堆的內(nèi)容。糟糕的堆管理可能會(huì)導(dǎo)致堆耗盡和GC抖動(dòng)等問(wèn)題。因此,人們開(kāi)發(fā)了許多工具來(lái)允許用戶(hù)自省他們的堆,例如Java Flight Recorder、jmap、YourKit和VisualVM工具。

大多數(shù)現(xiàn)有工具缺少的一個(gè)信息是特定分配的調(diào)用站點(diǎn)。堆轉(zhuǎn)儲(chǔ)和堆直方圖不包含此信息。此信息對(duì)于調(diào)試內(nèi)存問(wèn)題非常重要,因?yàn)樗嬖V開(kāi)發(fā)人員代碼中發(fā)生特定(特別糟糕的)分配的確切位置。

目前有兩種方法從熱點(diǎn)獲取這些信息:

  • 首先,您可以使用字節(jié)碼重寫(xiě)器(例如Allocation Instrumenter)來(lái)檢測(cè)應(yīng)用程序中的所有分配。然后,您可以讓插裝進(jìn)行堆棧跟蹤(當(dāng)您需要時(shí))。
  • 其次,您可以使用Java Flight Recorder,它在TLAB重新填充和直接分配到老一代時(shí)進(jìn)行堆棧跟蹤。這樣做的缺點(diǎn)是:a)它綁定到特定的分配實(shí)現(xiàn)(TLABs),并且錯(cuò)過(guò)了不符合該模式的分配;B)它不允許用戶(hù)自定義采樣間隔;c)它只記錄分配,所以你無(wú)法區(qū)分活對(duì)象和死對(duì)象。

該建議通過(guò)提供可擴(kuò)展的JVMTI接口來(lái)緩解這些問(wèn)題,該接口允許用戶(hù)定義采樣間隔并返回一組活動(dòng)堆棧跟蹤。

描述

新的JVMTI事件和方法

這里提出的面向用戶(hù)的堆采樣特性API由JVMTI的擴(kuò)展組成,該擴(kuò)展允許進(jìn)行堆分析。以下系統(tǒng)依賴(lài)于提供回調(diào)的事件通知系統(tǒng),例如:

void JNICALL
SampledObjectAlloc(jvmtiEnv *jvmti_env,
JNIEnv* jni_env,
jthread thread,
jobject object,
jclass object_klass,
jlong size)

說(shuō)明:

  • thread是分配對(duì)象的線程
  • object是對(duì)采樣對(duì)象的引用
  • object_klass是jobject的類(lèi)
  • size是分配的大小

新的API還包括一個(gè)新的JVMTI方法:

jvmtiError  SetHeapSamplingInterval(jvmtiEnv* env, jint sampling_interval)

其中sampling_interval是兩次采樣之間分配的平均字節(jié)數(shù)。該方法的規(guī)格為:

  • 如果不為零,采樣間隔將被更新,并將用sampling_interval字節(jié)的新平均采樣間隔發(fā)送回調(diào)給用戶(hù)
  • 例如,如果用戶(hù)希望每兆字節(jié)采樣一次,則sampling_interval將是1024 * 1024。
  • 如果將0傳遞給方法,采樣器在考慮到新的間隔后對(duì)每個(gè)分配進(jìn)行采樣,這可能需要一定數(shù)量的分配

注意,采樣間隔是不精確的。每次出現(xiàn)一個(gè)樣本時(shí),在下一個(gè)樣本被選擇之前的字節(jié)數(shù)將是給定平均間隔的偽隨機(jī)。這是為了避免抽樣偏差;例如,如果相同的分配每512KB發(fā)生一次,512KB采樣間隔將始終對(duì)相同的分配進(jìn)行采樣。因此,雖然采樣間隔并不總是選擇的間隔,但在大量的樣本之后,它會(huì)趨向于它。

用例示例

要啟用此功能,用戶(hù)將使用通常的事件通知調(diào)用來(lái)操作:

jvmti->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL)

該事件將在分配初始化并正確設(shè)置時(shí)發(fā)送,因此略晚于實(shí)際代碼執(zhí)行分配之后。缺省情況下,平均采樣間隔為512KB。

啟用采樣事件系統(tǒng)的最低要求是使用JVMTI_ENABLE和事件類(lèi)型
JVMTI_EVENT_SAMPLED_OBJECT_ALLOC調(diào)用SetEventNotificationMode。要修改采樣間隔,用戶(hù)調(diào)用SetHeapSamplingInterval方法。

禁用方式,

jvmti->SetEventNotificationMode(jvmti, JVMTI_DISABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL)

禁用事件通知并自動(dòng)禁用采樣器。

通過(guò)SetEventNotificationMode再次調(diào)用采樣器將使用當(dāng)前設(shè)置的采樣間隔重新啟用采樣器(默認(rèn)為512KB或用戶(hù)通過(guò)SetHeapSamplingInterval傳遞的最后一個(gè)值)。

新功能

為了保護(hù)新特性并使其成為VM實(shí)現(xiàn)的可選特性,在jvmtiCapabilities中引入了名為
can_generate_sampled_object_alloc_events的新功能。

全局/線程級(jí)采樣

使用通知系統(tǒng)提供了一種僅為特定線程發(fā)送事件的直接方法。這是通過(guò)SetEventNotificationMode完成的,并提供第三個(gè)參數(shù),其中包含要修改的線程。

完整的例子

下面的部分提供代碼片段來(lái)演示采樣器的API。首先,啟用功能和事件通知:

jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.SampledObjectAlloc = &SampledObjectAlloc;

jvmtiCapabilities caps;
memset(&caps, 0, sizeof(caps));
caps.can_generate_sampled_object_alloc_events = 1;
if (JVMTI_ERROR_NONE != (*jvmti)->AddCapabilities(jvmti, &caps)) {
return JNI_ERR;
}

if (JVMTI_ERROR_NONE != (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL)) {
return JNI_ERR;
}

if (JVMTI_ERROR_NONE != (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(jvmtiEventCallbacks)) {
return JNI_ERR;
}

// Set the sampler to 1MB.
if (JVMTI_ERROR_NONE != (*jvmti)->SetHeapSamplingInterval(jvmti, 1024 * 1024)) {
return JNI_ERR;
}

禁用采樣器(禁用事件和采樣器):

if (JVMTI_ERROR_NONE != (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_DISABLE,
JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL)) {
return JNI_ERR;
}

要重新啟用1024 * 1024字節(jié)采樣間隔的采樣器,需要一個(gè)簡(jiǎn)單的調(diào)用來(lái)啟用事件:

if (JVMTI_ERROR_NONE != (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL)) {
return JNI_ERR;
}

抽樣分配的用戶(hù)存儲(chǔ)

當(dāng)事件生成時(shí),回調(diào)可以使用JVMTI GetStackTrace方法捕獲堆棧跟蹤?;卣{(diào)獲得的jobject引用也可以包裝成JNI弱引用,以幫助確定對(duì)象何時(shí)已被垃圾收集。這種方法允許用戶(hù)收集關(guān)于采樣對(duì)象的數(shù)據(jù),以及仍然被認(rèn)為是活動(dòng)的對(duì)象的數(shù)據(jù),這是了解作業(yè)行為的好方法。

例如,可以這樣做:

extern "C" JNIEXPORT void JNICALL SampledObjectAlloc(jvmtiEnv *env,
JNIEnv* jni,
jthread thread,
jobject object,
jclass klass,
jlong size) {
jvmtiFrameInfo frames[32];
jint frame_count;
jvmtiError err;

err = global_jvmti->GetStackTrace(NULL, 0, 32, frames, &frame_count);
if (err == JVMTI_ERROR_NONE && frame_count >= 1) {
jweak ref = jni->NewWeakGlobalRef(object);
internal_storage.add(jni, ref, size, thread, frames, frame_count);
}
}

如果internal_storage是一個(gè)可以處理采樣對(duì)象的數(shù)據(jù)結(jié)構(gòu),請(qǐng)考慮是否需要清理任何垃圾收集的樣本,等等。該實(shí)現(xiàn)的內(nèi)部是特定于使用的,超出了這個(gè)JEP的范圍。

采樣間隔可以用作減少分析開(kāi)銷(xiāo)的一種手段。使用512KB的采樣間隔,開(kāi)銷(xiāo)應(yīng)該足夠低,用戶(hù)可以合理地在默認(rèn)情況下打開(kāi)系統(tǒng)。

實(shí)現(xiàn)細(xì)節(jié)

目前的原型和實(shí)現(xiàn)證明了該方法的可行性。它包括五個(gè)部分:

  1. 由于ThreadLocalAllocationBuffer (TLAB)結(jié)構(gòu)中字段名稱(chēng)的更改,導(dǎo)致架構(gòu)相關(guān)的更改。這些更改是最小的,因?yàn)樗鼈冎皇敲Q(chēng)更改。
  2. TLAB結(jié)構(gòu)增加了一個(gè)新的allocation_end指針,以補(bǔ)充現(xiàn)有的結(jié)束指針。如果禁用采樣,則兩個(gè)指針始終相等,代碼將像以前一樣執(zhí)行。如果啟用了采樣,end將被修改為請(qǐng)求下一個(gè)采樣點(diǎn)的位置。然后,任何快速路徑都會(huì)“認(rèn)為”TLAB在此時(shí)已經(jīng)滿(mǎn)了,然后沿著慢路徑走,這在(3)中解釋過(guò)。
  3. gc/shared/collectedHeap代碼被更改,因?yàn)樗挥米鞣峙渎窂降娜肟邳c(diǎn)。當(dāng)TLAB被認(rèn)為已滿(mǎn)(因?yàn)榉峙湟褌鬟f結(jié)束指針)時(shí),代碼進(jìn)入collectedHeap并嘗試分配一個(gè)新的TLAB。此時(shí),TLAB將恢復(fù)到其原始大小,并嘗試進(jìn)行分配。如果分配成功,代碼對(duì)分配進(jìn)行采樣,然后返回。如果沒(méi)有,則意味著TLAB的分配已經(jīng)結(jié)束,需要一個(gè)新的TLAB。代碼路徑繼續(xù)其對(duì)新TLAB的正常分配,并確定該分配是否需要示例。如果分配被認(rèn)為對(duì)TLAB來(lái)說(shuō)太大,系統(tǒng)也會(huì)對(duì)分配進(jìn)行抽樣,從而覆蓋TLAB分配內(nèi)和TLAB分配外進(jìn)行抽樣。
  4. 當(dāng)請(qǐng)求一個(gè)示例時(shí),堆棧上有一個(gè)收集器對(duì)象設(shè)置在一個(gè)安全的位置,用于將信息發(fā)送到本機(jī)代理。收集器跟蹤采樣分配,并在銷(xiāo)毀自己的幀時(shí)向代理發(fā)送回調(diào)。該機(jī)制確保對(duì)象被正確初始化。
  5. 如果JVMTI代理為SampledObjectAlloc事件注冊(cè)了回調(diào),則該事件將被觸發(fā),并且它將獲得抽樣分配。在libHeapMonitorTest.c文件中可以找到一個(gè)示例實(shí)現(xiàn),該文件用于JTreg測(cè)試。

選擇

對(duì)于這個(gè)JEP中提出的系統(tǒng),有多種替代方案。介紹中已經(jīng)介紹了兩個(gè):Flight Recorder提供了一個(gè)有趣的替代方案。這個(gè)實(shí)現(xiàn)提供了幾個(gè)優(yōu)點(diǎn)。首先,JFR不允許設(shè)置抽樣大小或提供回調(diào)。其次,當(dāng)緩沖區(qū)耗盡時(shí),JFR使用緩沖區(qū)系統(tǒng)可能導(dǎo)致分配丟失。最后,JFR事件系統(tǒng)沒(méi)有提供跟蹤已被垃圾收集的對(duì)象的方法,這意味著不可能使用它來(lái)提供有關(guān)活動(dòng)對(duì)象和垃圾收集對(duì)象的信息。

另一種替代方法是使用ASM的字節(jié)碼插裝。它的開(kāi)銷(xiāo)讓人望而卻步,不是一個(gè)可行的解決方案。

這個(gè)JEP向JVMTI添加了一個(gè)新特性,JVMTI是用于各種開(kāi)發(fā)和監(jiān)視工具的重要API/框架。有了它,JVMTI代理可以使用低開(kāi)銷(xiāo)的堆分析API以及其他JVMTI功能,這為工具提供了極大的靈活性。例如,由代理決定是否需要在每個(gè)事件點(diǎn)收集堆棧跟蹤。

測(cè)試

JTreg框架中針對(duì)該特性有16個(gè)測(cè)試:使用多個(gè)線程打開(kāi)/關(guān)閉,同時(shí)分配多個(gè)線程,測(cè)試數(shù)據(jù)是否以正確的間隔采樣,以及收集的堆棧是否反映正確的程序信息。

風(fēng)險(xiǎn)和假設(shè)

禁用該特性不會(huì)造成性能損失或風(fēng)險(xiǎn)。沒(méi)有啟用系統(tǒng)的用戶(hù)不會(huì)感知到性能差異。

但是,啟用該特性會(huì)有潛在的性能/內(nèi)存損失。在最初的原型實(shí)現(xiàn)中,開(kāi)銷(xiāo)是最小的(<2%)。這使用了一個(gè)更重量級(jí)的機(jī)制來(lái)修改JIT代碼。在這里給出的最終版本中,系統(tǒng)依賴(lài)于TLAB代碼,并且不應(yīng)該經(jīng)歷這種回歸。

目前對(duì)Dacapo基準(zhǔn)測(cè)試的評(píng)估顯示開(kāi)銷(xiāo)為:

  • 禁用時(shí)為0%
  • 1%,當(dāng)以默認(rèn)的512KB間隔啟用該特性,但不執(zhí)行回調(diào)動(dòng)作(即SampledAllocEvent方法為空,但已注冊(cè)到JVM)。
  • 3%開(kāi)銷(xiāo),使用抽樣回調(diào),執(zhí)行簡(jiǎn)單的實(shí)現(xiàn)來(lái)存儲(chǔ)數(shù)據(jù)(使用測(cè)試中的實(shí)現(xiàn))
責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2018-04-08 08:45:53

對(duì)象內(nèi)存策略

2023-09-27 08:49:23

.Net分配對(duì)象

2018-02-08 14:57:22

對(duì)象內(nèi)存分配

2012-01-11 10:45:57

JavaJVM

2015-11-16 11:22:05

Java對(duì)象內(nèi)存分配

2019-07-29 10:10:06

Java內(nèi)存線程安全

2021-07-30 07:22:51

JVM虛擬機(jī)棧 Stack

2019-09-04 15:31:04

JVM內(nèi)存String

2022-12-12 08:42:06

Java對(duì)象棧內(nèi)存

2010-09-25 15:40:52

配置JVM內(nèi)存

2013-11-07 09:42:42

對(duì)象對(duì)象池加速

2021-08-03 09:02:58

LinuxSlab算法

2009-07-09 10:01:26

設(shè)置JVM內(nèi)存分配

2025-04-11 08:42:10

Java對(duì)象TLAB

2009-10-27 17:39:39

Oracle用戶(hù)權(quán)限

2021-03-22 11:51:22

Java內(nèi)存棧上

2020-12-18 11:50:17

AI 數(shù)據(jù)人工智能

2017-08-28 10:13:09

國(guó)家分配對(duì)象

2025-03-03 09:05:56

2023-10-14 17:49:25

Java存儲(chǔ)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)