-
Bug
-
Resolution: Unresolved
-
P3
-
8u201
-
x86_64
-
linux_ubuntu
ADDITIONAL SYSTEM INFORMATION :
OS: ubuntu16.04 64bit
Java jdk1.8.0_201, jdk1.8.0_72, jdk1.8.0_66 and so on.
A DESCRIPTION OF THE PROBLEM :
"com/sun/javafx/event/EventDispatchChainImpl.java" reset() method doesn't "null clear" dispatchers[] array and that some elements holding reference in it is not GCed.
As the attached test program, Events are prepended to its dispatchers[] and are dispatched, but at some point when "chain.reset()" method is called to reset the array expecting all are "zero" or "null"cleared , but actually some elements of dispatches[] array are not null cleared.
We expect "dispatchers[]" array is all null filled out and no references are in dispatches[] array for the future GC, but actually there remains some references . This will lead to a memory leak.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1 To compile with jdk8, for example
$ /usr/java/jdk1.8.0_201/bin/javac EventDispatchChainImplTest.java
2 Execuing the compiled class
$ /usr/java/jdk1.8.0_201/jre/bin/java EventDispatchChainImplTest
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
==<test0>==
chain-1
chain-2
[null, null, null, null, null, null, null, null]
==<test1>==
[null, null, null, null, null, null, null, null]
ACTUAL -
==<test0>==
chain-1
chain-2
[null, EventDispatchChainImplTest$$Lambda$3/1828972342@5fd0d5ae, null, null, null, null, null, null]
==<test1>==
[null, null, EventDispatchChainImplTest$1$$Lambda$38/1297685781@48cf768c, null, null, null, null, null]
---------- BEGIN SOURCE ----------
import java.lang.reflect.Field;
import java.util.Arrays;
import com.sun.javafx.event.EventDispatchChainImpl;
import com.sun.javafx.event.EventUtil;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.scene.Group;
import javafx.scene.input.MouseEvent;
public class EventDispatchChainImplTest {
//
// "test0" is implemented with com.sun.javafx.event.*
//
public void test0() {
EventDispatchChainImpl chain = new EventDispatchChainImpl();
chain.prepend((e, h) -> {
System.out.println("chain-1");
h.prepend((e1, h1) -> {
System.out.println("chain-2");
return h1.dispatchEvent(e);
});
return h.dispatchEvent(e);
});
//Dispatces Event
chain.dispatchEvent(new Event(MouseEvent.ANY));
//Reset chain's properties
//And we expect "chain.dispatchers[] " array is filled with "null"
chain.reset();
//Check if "chain.dispatchers[]" is filled with "null" or not.
// But actually it is not null cleared. and it holding a reference. This is a bug.
logEventDispatchChainImpl(chain);
}
//
// "test1" is implemented with javafx.event.*
//
public void test1() {
Group g = new Group();
EventDispatcher dispatcher = new EventDispatcher() {
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
tail.prepend((e, d) -> {
return d.dispatchEvent(e);
});
return tail.dispatchEvent(event);
}
};
g.setEventDispatcher(dispatcher);
// Fire any event
Event.fireEvent(g, new Event(MouseEvent.ANY));
// Check if " EventUtil.eventDispatchChain.dispatchers" is filled with "null" or not.
// Actually it is not null cleared. and it holding a reference. This is a bug,too.
logEventUtil();
}
static void logEventUtil() {
try {
Field eventDispatchChainField = EventUtil.class.getDeclaredField("eventDispatchChain");
eventDispatchChainField.setAccessible(true);
EventDispatchChainImpl eventDispatchChainImpl = (EventDispatchChainImpl) eventDispatchChainField
.get(EventUtil.class);
logEventDispatchChainImpl(eventDispatchChainImpl);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
static void logEventDispatchChainImpl(EventDispatchChainImpl chain) {
try {
Field dispatchersField = EventDispatchChainImpl.class.getDeclaredField("dispatchers");
dispatchersField.setAccessible(true);
EventDispatcher[] dispatchers = (EventDispatcher[]) dispatchersField.get(chain);
System.out.println(Arrays.toString(dispatchers));
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
EventDispatchChainImplTest test = new EventDispatchChainImplTest();
System.out.println("==<test0>==");
test.test0();
System.out.println("==<test1>==");
test.test1();
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
As a workaround , I modified reset() method in com.sun.javafx.event.EventDispatchChainImpl.java
Original:
public void reset() {
// shrink?
! for (int i = 0; i < reservedCount; ++i) {
dispatchers[i] = null;
}
My workaround:
public void reset() {
// shrink?
! for (int i = 0; i < dispatchers.length; ++i) {
dispatchers[i] = null;
}
. As of now, it looks it's working.
FREQUENCY : always
OS: ubuntu16.04 64bit
Java jdk1.8.0_201, jdk1.8.0_72, jdk1.8.0_66 and so on.
A DESCRIPTION OF THE PROBLEM :
"com/sun/javafx/event/EventDispatchChainImpl.java" reset() method doesn't "null clear" dispatchers[] array and that some elements holding reference in it is not GCed.
As the attached test program, Events are prepended to its dispatchers[] and are dispatched, but at some point when "chain.reset()" method is called to reset the array expecting all are "zero" or "null"cleared , but actually some elements of dispatches[] array are not null cleared.
We expect "dispatchers[]" array is all null filled out and no references are in dispatches[] array for the future GC, but actually there remains some references . This will lead to a memory leak.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1 To compile with jdk8, for example
$ /usr/java/jdk1.8.0_201/bin/javac EventDispatchChainImplTest.java
2 Execuing the compiled class
$ /usr/java/jdk1.8.0_201/jre/bin/java EventDispatchChainImplTest
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
==<test0>==
chain-1
chain-2
[null, null, null, null, null, null, null, null]
==<test1>==
[null, null, null, null, null, null, null, null]
ACTUAL -
==<test0>==
chain-1
chain-2
[null, EventDispatchChainImplTest$$Lambda$3/1828972342@5fd0d5ae, null, null, null, null, null, null]
==<test1>==
[null, null, EventDispatchChainImplTest$1$$Lambda$38/1297685781@48cf768c, null, null, null, null, null]
---------- BEGIN SOURCE ----------
import java.lang.reflect.Field;
import java.util.Arrays;
import com.sun.javafx.event.EventDispatchChainImpl;
import com.sun.javafx.event.EventUtil;
import javafx.event.Event;
import javafx.event.EventDispatchChain;
import javafx.event.EventDispatcher;
import javafx.scene.Group;
import javafx.scene.input.MouseEvent;
public class EventDispatchChainImplTest {
//
// "test0" is implemented with com.sun.javafx.event.*
//
public void test0() {
EventDispatchChainImpl chain = new EventDispatchChainImpl();
chain.prepend((e, h) -> {
System.out.println("chain-1");
h.prepend((e1, h1) -> {
System.out.println("chain-2");
return h1.dispatchEvent(e);
});
return h.dispatchEvent(e);
});
//Dispatces Event
chain.dispatchEvent(new Event(MouseEvent.ANY));
//Reset chain's properties
//And we expect "chain.dispatchers[] " array is filled with "null"
chain.reset();
//Check if "chain.dispatchers[]" is filled with "null" or not.
// But actually it is not null cleared. and it holding a reference. This is a bug.
logEventDispatchChainImpl(chain);
}
//
// "test1" is implemented with javafx.event.*
//
public void test1() {
Group g = new Group();
EventDispatcher dispatcher = new EventDispatcher() {
@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
tail.prepend((e, d) -> {
return d.dispatchEvent(e);
});
return tail.dispatchEvent(event);
}
};
g.setEventDispatcher(dispatcher);
// Fire any event
Event.fireEvent(g, new Event(MouseEvent.ANY));
// Check if " EventUtil.eventDispatchChain.dispatchers" is filled with "null" or not.
// Actually it is not null cleared. and it holding a reference. This is a bug,too.
logEventUtil();
}
static void logEventUtil() {
try {
Field eventDispatchChainField = EventUtil.class.getDeclaredField("eventDispatchChain");
eventDispatchChainField.setAccessible(true);
EventDispatchChainImpl eventDispatchChainImpl = (EventDispatchChainImpl) eventDispatchChainField
.get(EventUtil.class);
logEventDispatchChainImpl(eventDispatchChainImpl);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
static void logEventDispatchChainImpl(EventDispatchChainImpl chain) {
try {
Field dispatchersField = EventDispatchChainImpl.class.getDeclaredField("dispatchers");
dispatchersField.setAccessible(true);
EventDispatcher[] dispatchers = (EventDispatcher[]) dispatchersField.get(chain);
System.out.println(Arrays.toString(dispatchers));
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
EventDispatchChainImplTest test = new EventDispatchChainImplTest();
System.out.println("==<test0>==");
test.test0();
System.out.println("==<test1>==");
test.test1();
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
As a workaround , I modified reset() method in com.sun.javafx.event.EventDispatchChainImpl.java
Original:
public void reset() {
// shrink?
! for (int i = 0; i < reservedCount; ++i) {
dispatchers[i] = null;
}
My workaround:
public void reset() {
// shrink?
! for (int i = 0; i < dispatchers.length; ++i) {
dispatchers[i] = null;
}
. As of now, it looks it's working.
FREQUENCY : always