JVMTI callback SampledObjectAlloc always fires for first allocation in a new thread
Markus Gaisbauer
markus.gaisbauer at gmail.com
Wed Jun 17 07:57:45 UTC 2020
Hi Jean,
Thank you for having a look at this.
I attached the code of my basic JVMTI agent. I ran my tests on Windows.
Maybe this 0xf1f1f1f1f1f1f1f1 is a Windows thing and Linux initializes the
memory to all zeros.
Regards,
Markus
On Tue, Jun 16, 2020 at 2:25 AM Jean Christophe Beyler <jcbeyler at google.com>
wrote:
> Hi Markus,
>
> I played around adding your Java code in the testing framework and I don't
> get exactly the same failure as you do. Basically, I get about 5% samples
> compared to the number of threads, whereas you seem to get a sample for
> each element. Could you add the code you used for the agent so I can see if
> you are doing something different than I am in that regard?
>
> This doesn't change the issue, I'm just curious why you seem to be
> exposing it more. I'm still digging into what would be the right solution
> for this.
>
> Thanks,
> Jc
>
> On Mon, Jun 15, 2020 at 9:53 AM Jean Christophe Beyler <
> jcbeyler at google.com> wrote:
>
>> Hi Markus,
>>
>> I created:
>> https://bugs.openjdk.java.net/browse/JDK-8247615
>>
>> And I'll see what needs to be done for it :)
>> Jc
>>
>> On Fri, Jun 5, 2020 at 3:45 AM Markus Gaisbauer <
>> markus.gaisbauer at gmail.com> wrote:
>>
>>> Hi,
>>>
>>> JVMTI callback SampledObjectAlloc is currently always called for the
>>> first allocation of a thread. This generates a lot of bias in an
>>> application that regularly starts new threads.
>>>
>>> I tested this with latest Java 11 and Java 15.
>>>
>>> E.g. here is a sample that creates 100 threads and allocates one object
>>> in each thread.
>>>
>>> public class AllocationProfilingBiasReproducer {
>>> public static void main(String[] args) throws Exception {
>>> for (int i = 0; i < 100; i++) {
>>> new Thread(new Task(), "Task " + i).start();
>>> Thread.sleep(1);
>>> }
>>> Thread.sleep(1000);
>>> }
>>> private static class Task implements Runnable {
>>> @Override
>>> public void run() {
>>> new A();
>>> }
>>> }
>>> private static class A {
>>> }
>>> }
>>>
>>> I built a simple JVMTI agent that registers SampledObjectAlloc callback
>>> and sets interval to 1 MB with SetHeapSamplingInterval. The callback simply
>>> logs thread name and class name of allocated object.
>>>
>>> I see the following output:
>>>
>>> SampledObjectAlloc Ljava/lang/String; via Task 0
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 1
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 2
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 3
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 4
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 5
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 6
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 7
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 8
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 9
>>> SampledObjectAlloc LAllocationProfilingBiasReproducer$A; via Task 10
>>> ...
>>>
>>> This is not expected.
>>>
>>> I set a breakpoint in my SampledObjectAlloc callback and observed the
>>> following:
>>>
>>> In MemAllocator::Allocation::notify_allocation_jvmti_sampler() the local
>>> var bytes_since_last is always 0xf1f1f1f1f1f1f1f1 for first allocation of a
>>> thread. So first allocation is always reported to my agent.
>>>
>>> ThreadLocalAllocBuffer::_bytes_since_last_sample_point does not seem to
>>> be explicitly initialized before accessing it for the first time. I assume
>>> 0xf1f1f1f1f1f1f1f1 is a default value provided by some Hotspot allocator.
>>> Only after the first event fired, notify_allocation_jvmti_sampler
>>> calls ThreadLocalAllocBuffer::set_sample_end which initializes
>>> _bytes_since_last_sample_point to a proper value.
>>>
>>> I am looking for someone who could create a JIRA ticket for this.
>>>
>>> Regards,
>>> Markus
>>>
>>
>>
>> --
>>
>> Thanks,
>> Jc
>>
>
>
> --
>
> Thanks,
> Jc
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/serviceability-dev/attachments/20200617/47121d12/attachment-0001.htm>
-------------- next part --------------
#include <jni.h>
#include <jvmti.h>
#include <stdlib.h>
#include <stdio.h>
#include <cstring>
#include <string>
void checkError(jvmtiEnv* jvmti, jvmtiError error) {
if (error != JVMTI_ERROR_NONE) {
char* errorName = nullptr;
jvmti->GetErrorName(error, &errorName);
fprintf(stderr, "%s\n", errorName);
exit(1);
}
}
static void JNICALL SampledObjectAlloc(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jobject object, jclass object_klass, jlong size) {
jvmtiThreadInfo threadInfo;
jvmtiError error = jvmti->GetThreadInfo(thread, &threadInfo);
checkError(jvmti, error);
std::string threadName = threadInfo.name ? threadInfo.name : "";
char* classSignaturePtr = nullptr;
error = jvmti->GetClassSignature(object_klass, &classSignaturePtr, nullptr);
checkError(jvmti, error);
std::string classSignature = classSignaturePtr ? classSignaturePtr : "";
fprintf(stderr, "SampledObjectAlloc %s via %s\n", classSignature.c_str(), threadName.c_str());
jvmti->Deallocate((unsigned char*) threadInfo.name);
jni->DeleteLocalRef(threadInfo.context_class_loader);
jni->DeleteLocalRef(threadInfo.thread_group);
}
extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
jvmtiEnv* jvmti = nullptr;
jint const r = jvm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_2);
if (r != JNI_OK) {
fprintf(stderr, "GetEnv(JVMTI_VERSION_1_2) failed with %d.\n", r);
}
jvmtiError error;
jvmtiCapabilities capabilities;
memset(&capabilities, 0, sizeof(capabilities));
capabilities.can_generate_sampled_object_alloc_events = 1;
jvmti->AddCapabilities(&capabilities);
jvmtiEventCallbacks callbacks {};
callbacks.SampledObjectAlloc = SampledObjectAlloc;
error = jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks));
checkError(jvmti, error);
error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, nullptr);
checkError(jvmti, error);
jvmti->SetHeapSamplingInterval(1024 * 1024);
return JNI_OK;
}
extern "C" JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *jvm) {
}
More information about the serviceability-dev
mailing list