Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-2042736 | 1.4.0 | Mandy Chung | P1 | Resolved | Fixed | beta2 |
This problem is reproducable only with the latest release (1.3.1-rc1) of hotspot JVM. It works as expected with Classic VM. From Incident Manager (Incident : 120869 ). See "Suggested Fix" and "Comments".
Bug Description: The JVMPI spec
(http://java.sun.com/j2se/1.3/docs/guide/jvmpi/jvmpi.html)
states that: "Assuming the defining events are enabled during the
profiler initialization, the profiler agent is guaranteed to be
notified of an entity's creation through a defining event, before
the entity appears in other JVMPI events. "
However in 1.3.1RC1, both C1 and C2, it seems that many events
during JVM startup are not sent, resulting in lots of undefined
IDs for the JVMPI client, despite the defining events being enabled
since JVM_OnLoad(). In fact, running with -verbose shows quite a
bit of activity before JVM_OnLoad() is even called. This would
appear to be a violation of the spec.
Although we can request the defining events, this changes the model
we are used to, and will have as-yet-unknown impact on our product
because we will be receiving defining events at different times and
in different contexts.
The following fairly simple client demonstrates the problem with
object IDs, but we also see it with class ids, and method ids in
stack traces. The client tracks object IDs, and at JVM init time
requests a heap dump (level 0). It walks the heap dump and prints
out an error if it has not seen the ID. Errors will also be printed
if, for instance, an object alloc event is sent with an arena ID
for which there has been no defining event.
Compile and run with "java -Xrunjvmpi_tool SomeClass" (SomeClass may
even be bogus). Comapre the output with C1 or C2 (everything in the
dump is unknown) versus the Classic VM (everything in the dump is
known). There may be errors in the overall object tracking, I just
whipped this up, but it seems correct for Classic so I doubt it's
horribly wrong.
Obviously, our real JVMPI client doesn't ask for a dump right at
start up, but it does ask for regular stack traces and monitor
dumps, and can ask for heap dumps at arbitrary time under user
control.
#include <stdio.h>
#include <jvmpi.h>
#define JVMPI(m) (jvmpi->m)
struct obj_info {
obj_info(JVMPI_Event * e) { init(e); }
void init(JVMPI_Event * e);
jobjectID id;
obj_info * next;
obj_info * prev;
jint arena_id;
obj_info * arena_next;
obj_info * arena_prev;
};
obj_info * obj_list;
obj_info * obj_free;
struct arena_info {
arena_info(jint id) { init(id); }
void init(jint id);
jint id;
obj_info * head;
arena_info * next;
arena_info * prev;
};
arena_info * arena_list;
arena_info * arena_free;
void
arena_info::init(jint arena_id)
{
id = arena_id;
head = NULL;
next = arena_list;
prev = NULL;
if (next) {
next->prev = this;
}
arena_list = this;
}
arena_info *
find_arena(jint id)
{
arena_info * cur = arena_list;
while (cur && cur->id != id) {
cur = cur->next;
}
return cur;
}
arena_info *
new_arena(jint id)
{
arena_info * arena;
if (arena_free) {
arena = arena_free;
arena_free = arena->next;
arena->init(id);
} else {
arena = new arena_info(id);
}
return arena;
}
void
arena_add(obj_info * obj)
{
arena_info * arena = find_arena(obj->arena_id);
if (!arena) {
fprintf(stderr, "No arena %ld to add/move object %p
to\n",
(long)obj->arena_id, (void *)obj->id);
arena = new_arena(obj->arena_id);
}
obj->arena_prev = NULL;
obj->arena_next = arena->head;
if (obj->arena_next) {
obj->arena_next->prev = obj;
}
arena->head = obj;
}
void
arena_delete_obj(obj_info * obj)
{
arena_info * arena = find_arena(obj->arena_id);
if (arena) {
if (obj->arena_prev) {
obj->arena_prev->arena_next = obj->arena_next;
} else {
arena->head = obj->arena_next;
}
if (obj->arena_next) {
obj->arena_next->arena_prev = obj->arena_prev;
}
}
}
void
arena_move_obj(obj_info * obj, jint new_id)
{
arena_delete_obj(obj);
obj->arena_id = new_id;
arena_add(obj);
}
void
obj_info::init(JVMPI_Event * e)
{
id = e->u.obj_alloc.obj_id;
arena_id = e->u.obj_alloc.arena_id;
next = obj_list;
prev = NULL;
arena_add(this);
if (next) {
next->prev = this;
}
obj_list = this;
}
obj_info *
find_obj(jobjectID id)
{
obj_info * cur = obj_list;
while (cur && cur->id != id) {
cur = cur->next;
}
return cur;
}
void
delete_obj(jobjectID id)
{
obj_info * obj = find_obj(id);
if (!obj) {
fprintf(stderr, "Can't delete %p; no defining event\n",
(void
*)id);
} else {
// Remove from object list
if (obj->prev) {
obj->prev->next = obj->next;
} else {
obj_list = obj->next;
}
if (obj->next) {
obj->next->prev = obj->prev;
}
// Remove from arena list
arena_delete_obj(obj);
// Add to free list
obj->next = obj_free;
obj_free = obj;
}
}
void
move_obj(jobjectID old_id, jobjectID new_id, jint new_arena)
{
obj_info * obj = find_obj(old_id);
if (!obj) {
fprintf(stderr, "Can't move %p; no defining event\n",
(void
*)old_id);
} else {
obj->id = new_id;
arena_move_obj(obj, new_arena);
}
}
obj_info *
add_obj(JVMPI_Event * e)
{
obj_info * obj = NULL;
if (obj_free) {
obj = obj_free;
obj_free = obj_free->next;
obj->init(e);
} else {
obj = new obj_info(e);
}
return obj;
}
void
arena_delete(jint id)
{
arena_info * arena = find_arena(id);
if (!arena) {
fprintf(stderr, "Cannot delete arena %ld; no defining
event\n",
(long)id);
} else {
// Delete all objects
while (arena->head) {
delete_obj(arena->head->id);
}
// Remove from arena list
if (arena->prev) {
arena->prev->next = arena->next;
} else {
arena_list = arena->next;
}
if (arena->next) {
arena->next->prev = arena->prev;
}
// Add to free list
arena->next = arena_free;
arena_free = arena;
}
}
void
check_dump(char * begin, void * end)
{
int total = 0;
int bad = 0;
jobjectID id;
obj_info * obj;
while (begin + 1 + sizeof(jobjectID) <= end) {
memcpy((void *)&id, begin + 1, sizeof(jobjectID));
begin += 1 + sizeof(jobjectID);
++total;
if (find_obj(id) == NULL) {
++bad;
fprintf(stderr, "Unknown object %p in heap
dump\n",
(void *)id);
}
}
if (begin < end) {
fprintf(stderr, "Incomplete record at end of heap dump:
%p -
%p\n",
(void *)begin, (void *)end);
}
printf("\nHeap dump had %d objects, %d bad\n", total, bad);
}
JVMPI_RawMonitor obj_lock;
static JVMPI_Interface *jvmpi;
static void notify(JVMPI_Event *);
extern "C" JNIEXPORT jint JNICALL
JVM_OnLoad(JavaVM *jvm, char *options, void *reserved)
{
int res = jvm->GetEnv((void **)&jvmpi, JVMPI_VERSION_1);
if (res < 0) {
return JNI_ERR;
}
jvmpi->NotifyEvent = notify;
JVMPI(EnableEvent)(JVMPI_EVENT_JVM_INIT_DONE, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_JVM_SHUT_DOWN, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_ARENA_NEW, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_ARENA_DELETE, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_OBJECT_ALLOC, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_OBJECT_MOVE, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_OBJECT_FREE, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_GC_START, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_GC_FINISH, NULL);
obj_lock = JVMPI(RawMonitorCreate)("Object info lock");
return JNI_OK;
}
static JVMPI_HeapDumpArg dump_arg = { JVMPI_DUMP_LEVEL_0 };
void notify(JVMPI_Event * event)
{
switch (event->event_type) {
case JVMPI_EVENT_JVM_INIT_DONE:
JVMPI(RequestEvent)(JVMPI_EVENT_HEAP_DUMP, (void *)&dump_arg);
break;
case JVMPI_EVENT_JVM_SHUT_DOWN:
putchar('\n');
break;
case JVMPI_EVENT_ARENA_NEW:
JVMPI(RawMonitorEnter)(obj_lock);
printf("\nNew arena %ld\n", (long)event->u.new_arena.arena_id);
new_arena(event->u.new_arena.arena_id);
JVMPI(RawMonitorExit)(obj_lock);
break;
case JVMPI_EVENT_ARENA_DELETE:
JVMPI(RawMonitorEnter)(obj_lock);
printf("\nDelete arena %ld\n", (long)event->u.delete_arena.arena
_id);
arena_delete(event->u.delete_arena.arena_id);
JVMPI(RawMonitorExit)(obj_lock);
break;
case JVMPI_EVENT_OBJECT_ALLOC:
JVMPI(RawMonitorEnter)(obj_lock);
putchar('+');
add_obj(event);
JVMPI(RawMonitorExit)(obj_lock);
break;
case JVMPI_EVENT_GC_START:
JVMPI(RawMonitorEnter)(obj_lock);
puts("\nGC Start:\n");
break;
case JVMPI_EVENT_OBJECT_MOVE:
putchar('~');
move_obj(event->u.obj_move.obj_id, event->u.obj_move.new_obj_id,
event->u.obj_move.new_arena_id);
break;
case JVMPI_EVENT_OBJECT_FREE:
delete_obj(event->u.obj_free.obj_id);
putchar('-');
break;
case JVMPI_EVENT_GC_FINISH:
puts("\nGC finish\n");
JVMPI(RawMonitorExit)(obj_lock);
break;
case JVMPI_REQUESTED_EVENT|JVMPI_EVENT_HEAP_DUMP:
JVMPI(RawMonitorEnter)(obj_lock);
check_dump(event->u.heap_dump.begin, event->u.heap_dump.end);
JVMPI(RawMonitorExit)(obj_lock);
break;
default:
break;
}
}
Bug Description: The JVMPI spec
(http://java.sun.com/j2se/1.3/docs/guide/jvmpi/jvmpi.html)
states that: "Assuming the defining events are enabled during the
profiler initialization, the profiler agent is guaranteed to be
notified of an entity's creation through a defining event, before
the entity appears in other JVMPI events. "
However in 1.3.1RC1, both C1 and C2, it seems that many events
during JVM startup are not sent, resulting in lots of undefined
IDs for the JVMPI client, despite the defining events being enabled
since JVM_OnLoad(). In fact, running with -verbose shows quite a
bit of activity before JVM_OnLoad() is even called. This would
appear to be a violation of the spec.
Although we can request the defining events, this changes the model
we are used to, and will have as-yet-unknown impact on our product
because we will be receiving defining events at different times and
in different contexts.
The following fairly simple client demonstrates the problem with
object IDs, but we also see it with class ids, and method ids in
stack traces. The client tracks object IDs, and at JVM init time
requests a heap dump (level 0). It walks the heap dump and prints
out an error if it has not seen the ID. Errors will also be printed
if, for instance, an object alloc event is sent with an arena ID
for which there has been no defining event.
Compile and run with "java -Xrunjvmpi_tool SomeClass" (SomeClass may
even be bogus). Comapre the output with C1 or C2 (everything in the
dump is unknown) versus the Classic VM (everything in the dump is
known). There may be errors in the overall object tracking, I just
whipped this up, but it seems correct for Classic so I doubt it's
horribly wrong.
Obviously, our real JVMPI client doesn't ask for a dump right at
start up, but it does ask for regular stack traces and monitor
dumps, and can ask for heap dumps at arbitrary time under user
control.
#include <stdio.h>
#include <jvmpi.h>
#define JVMPI(m) (jvmpi->m)
struct obj_info {
obj_info(JVMPI_Event * e) { init(e); }
void init(JVMPI_Event * e);
jobjectID id;
obj_info * next;
obj_info * prev;
jint arena_id;
obj_info * arena_next;
obj_info * arena_prev;
};
obj_info * obj_list;
obj_info * obj_free;
struct arena_info {
arena_info(jint id) { init(id); }
void init(jint id);
jint id;
obj_info * head;
arena_info * next;
arena_info * prev;
};
arena_info * arena_list;
arena_info * arena_free;
void
arena_info::init(jint arena_id)
{
id = arena_id;
head = NULL;
next = arena_list;
prev = NULL;
if (next) {
next->prev = this;
}
arena_list = this;
}
arena_info *
find_arena(jint id)
{
arena_info * cur = arena_list;
while (cur && cur->id != id) {
cur = cur->next;
}
return cur;
}
arena_info *
new_arena(jint id)
{
arena_info * arena;
if (arena_free) {
arena = arena_free;
arena_free = arena->next;
arena->init(id);
} else {
arena = new arena_info(id);
}
return arena;
}
void
arena_add(obj_info * obj)
{
arena_info * arena = find_arena(obj->arena_id);
if (!arena) {
fprintf(stderr, "No arena %ld to add/move object %p
to\n",
(long)obj->arena_id, (void *)obj->id);
arena = new_arena(obj->arena_id);
}
obj->arena_prev = NULL;
obj->arena_next = arena->head;
if (obj->arena_next) {
obj->arena_next->prev = obj;
}
arena->head = obj;
}
void
arena_delete_obj(obj_info * obj)
{
arena_info * arena = find_arena(obj->arena_id);
if (arena) {
if (obj->arena_prev) {
obj->arena_prev->arena_next = obj->arena_next;
} else {
arena->head = obj->arena_next;
}
if (obj->arena_next) {
obj->arena_next->arena_prev = obj->arena_prev;
}
}
}
void
arena_move_obj(obj_info * obj, jint new_id)
{
arena_delete_obj(obj);
obj->arena_id = new_id;
arena_add(obj);
}
void
obj_info::init(JVMPI_Event * e)
{
id = e->u.obj_alloc.obj_id;
arena_id = e->u.obj_alloc.arena_id;
next = obj_list;
prev = NULL;
arena_add(this);
if (next) {
next->prev = this;
}
obj_list = this;
}
obj_info *
find_obj(jobjectID id)
{
obj_info * cur = obj_list;
while (cur && cur->id != id) {
cur = cur->next;
}
return cur;
}
void
delete_obj(jobjectID id)
{
obj_info * obj = find_obj(id);
if (!obj) {
fprintf(stderr, "Can't delete %p; no defining event\n",
(void
*)id);
} else {
// Remove from object list
if (obj->prev) {
obj->prev->next = obj->next;
} else {
obj_list = obj->next;
}
if (obj->next) {
obj->next->prev = obj->prev;
}
// Remove from arena list
arena_delete_obj(obj);
// Add to free list
obj->next = obj_free;
obj_free = obj;
}
}
void
move_obj(jobjectID old_id, jobjectID new_id, jint new_arena)
{
obj_info * obj = find_obj(old_id);
if (!obj) {
fprintf(stderr, "Can't move %p; no defining event\n",
(void
*)old_id);
} else {
obj->id = new_id;
arena_move_obj(obj, new_arena);
}
}
obj_info *
add_obj(JVMPI_Event * e)
{
obj_info * obj = NULL;
if (obj_free) {
obj = obj_free;
obj_free = obj_free->next;
obj->init(e);
} else {
obj = new obj_info(e);
}
return obj;
}
void
arena_delete(jint id)
{
arena_info * arena = find_arena(id);
if (!arena) {
fprintf(stderr, "Cannot delete arena %ld; no defining
event\n",
(long)id);
} else {
// Delete all objects
while (arena->head) {
delete_obj(arena->head->id);
}
// Remove from arena list
if (arena->prev) {
arena->prev->next = arena->next;
} else {
arena_list = arena->next;
}
if (arena->next) {
arena->next->prev = arena->prev;
}
// Add to free list
arena->next = arena_free;
arena_free = arena;
}
}
void
check_dump(char * begin, void * end)
{
int total = 0;
int bad = 0;
jobjectID id;
obj_info * obj;
while (begin + 1 + sizeof(jobjectID) <= end) {
memcpy((void *)&id, begin + 1, sizeof(jobjectID));
begin += 1 + sizeof(jobjectID);
++total;
if (find_obj(id) == NULL) {
++bad;
fprintf(stderr, "Unknown object %p in heap
dump\n",
(void *)id);
}
}
if (begin < end) {
fprintf(stderr, "Incomplete record at end of heap dump:
%p -
%p\n",
(void *)begin, (void *)end);
}
printf("\nHeap dump had %d objects, %d bad\n", total, bad);
}
JVMPI_RawMonitor obj_lock;
static JVMPI_Interface *jvmpi;
static void notify(JVMPI_Event *);
extern "C" JNIEXPORT jint JNICALL
JVM_OnLoad(JavaVM *jvm, char *options, void *reserved)
{
int res = jvm->GetEnv((void **)&jvmpi, JVMPI_VERSION_1);
if (res < 0) {
return JNI_ERR;
}
jvmpi->NotifyEvent = notify;
JVMPI(EnableEvent)(JVMPI_EVENT_JVM_INIT_DONE, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_JVM_SHUT_DOWN, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_ARENA_NEW, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_ARENA_DELETE, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_OBJECT_ALLOC, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_OBJECT_MOVE, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_OBJECT_FREE, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_GC_START, NULL);
JVMPI(EnableEvent)(JVMPI_EVENT_GC_FINISH, NULL);
obj_lock = JVMPI(RawMonitorCreate)("Object info lock");
return JNI_OK;
}
static JVMPI_HeapDumpArg dump_arg = { JVMPI_DUMP_LEVEL_0 };
void notify(JVMPI_Event * event)
{
switch (event->event_type) {
case JVMPI_EVENT_JVM_INIT_DONE:
JVMPI(RequestEvent)(JVMPI_EVENT_HEAP_DUMP, (void *)&dump_arg);
break;
case JVMPI_EVENT_JVM_SHUT_DOWN:
putchar('\n');
break;
case JVMPI_EVENT_ARENA_NEW:
JVMPI(RawMonitorEnter)(obj_lock);
printf("\nNew arena %ld\n", (long)event->u.new_arena.arena_id);
new_arena(event->u.new_arena.arena_id);
JVMPI(RawMonitorExit)(obj_lock);
break;
case JVMPI_EVENT_ARENA_DELETE:
JVMPI(RawMonitorEnter)(obj_lock);
printf("\nDelete arena %ld\n", (long)event->u.delete_arena.arena
_id);
arena_delete(event->u.delete_arena.arena_id);
JVMPI(RawMonitorExit)(obj_lock);
break;
case JVMPI_EVENT_OBJECT_ALLOC:
JVMPI(RawMonitorEnter)(obj_lock);
putchar('+');
add_obj(event);
JVMPI(RawMonitorExit)(obj_lock);
break;
case JVMPI_EVENT_GC_START:
JVMPI(RawMonitorEnter)(obj_lock);
puts("\nGC Start:\n");
break;
case JVMPI_EVENT_OBJECT_MOVE:
putchar('~');
move_obj(event->u.obj_move.obj_id, event->u.obj_move.new_obj_id,
event->u.obj_move.new_arena_id);
break;
case JVMPI_EVENT_OBJECT_FREE:
delete_obj(event->u.obj_free.obj_id);
putchar('-');
break;
case JVMPI_EVENT_GC_FINISH:
puts("\nGC finish\n");
JVMPI(RawMonitorExit)(obj_lock);
break;
case JVMPI_REQUESTED_EVENT|JVMPI_EVENT_HEAP_DUMP:
JVMPI(RawMonitorEnter)(obj_lock);
check_dump(event->u.heap_dump.begin, event->u.heap_dump.end);
JVMPI(RawMonitorExit)(obj_lock);
break;
default:
break;
}
}
- backported by
-
JDK-2042736 Many JVMPI IDs lack defining event. JVM on startup should send them to Clients.
- Resolved
- relates to
-
JDK-4338535 CLASS_LOAD events missing for early classes
- Closed