[
https://issues.apache.org/jira/browse/CALCITE-3325?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16924634#comment-16924634
]
Mehdi Salarkia commented on CALCITE-3325:
-----------------------------------------
Thanks [~elserj] for your quick response and looking into this.
I think I have to first explain the test environment so it would be clear. We
have a set of Phoenix query severs and a set of clients talking to the query
servers via protobuf protocol and Avatica.
The client code runs on jetty which has a fixed thread pool for serving the
requests. We use a client side connection pool Avatica connections. The
connections could be recycled and used between threads but the connection pool
could also create/drop connections as necessary.
What we have observed was a excessive garbage collection when service is under
load for a some time. From what we can see is there are a lot of
UnsynchronizedBuffer instances for each thread (please see the two attached
snapshots) which shouldn't be the case if they were suppose to be recycled and
reused by same thread. You are right that the size of these object is
insignificant but that's because garbage collection has already cleaned up many
of the instances and needs to constantly do it in order that application can
maintain its memory. From what we can see right now we have a high CPU
utilization due to this issue.
>From my understanding objects stored in ThreadLocal are not removed until a
>garbage collection happens due to insufficient memory, see in
>java.lang.ThreadLocal.ThreadLocalMap:
{code:java}
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap
{code}
I wrote a test case to explain the scenario :
{code:java}
import java.util.Random;
import java.util.UUID;
public class ThreadLocalTest {
private final ThreadLocal<UnsynchronizedBuffer> THREAD_LOCAL_BUFFER =
new ThreadLocal<UnsynchronizedBuffer>() {
@Override
protected UnsynchronizedBuffer initialValue() {
return new UnsynchronizedBuffer();
}
};
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
ThreadLocalTest test = new ThreadLocalTest();
UnsynchronizedBuffer buffer =
test.THREAD_LOCAL_BUFFER.get();
buffer.doSomething();
Thread.yield();
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
}
static class UnsynchronizedBuffer {
byte[] data = new byte[100000];
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Clean up");
}
public void doSomething() {
}
}
}
{code}
And to make it to show the impact, I ran it with the following configuration
{code:java}
-Xms10m -Xmx10m
{code}
and captured two snapshots with Yourkit one for `THREAD_LOCAL_BUFFER` defined
as `non static` and the other one as `static` (Please see
attached[^Non-Static.snapshot] & Static.snapshot).
If you look at the garbage collection metrics for the two snapshots for GC
metrics (Non-Static.png & Static.png) You could see the impact I'm talking
about.
Also by simply running the code above you will see the console will quickly
start to show the "Clean up" message which shouldn't be the case as the design
that you had was to be able to re-use the object stored in threadLocal but what
is happening in reality is the UnsynchronizedBuffer is deleted and recreated
for each time any of the threads is trying to use it.
Hope this explains the problem in more details.
Thanks.
> Thread Local Buffer Variable (threadLocalBuffer) In ProtobufTranslationImpl
> Is Defined As Non Static Causing Memory Leak
> ------------------------------------------------------------------------------------------------------------------------
>
> Key: CALCITE-3325
> URL: https://issues.apache.org/jira/browse/CALCITE-3325
> Project: Calcite
> Issue Type: Bug
> Reporter: Mehdi Salarkia
> Priority: Major
> Attachments: Non-Static.snapshot, Non-static.png, Screen Shot
> 2019-09-05 at 5.05.20 PM.png, Screen Shot 2019-09-05 at 5.18.19 PM.png,
> Static.png, Static.snapshot
>
>
> As we were load testing our system on Apache Phoenix via the thin client
> which uses Avatica we ran into Garbage collection problems. After some
> investigation we could see there are a lot of unreferenced object due to this
> variable:
> {code:java}
> private final ThreadLocal<UnsynchronizedBuffer> threadLocalBuffer =
> new ThreadLocal<UnsynchronizedBuffer>() {
> @Override protected UnsynchronizedBuffer initialValue() {
> return new UnsynchronizedBuffer();
> }
> };
> {code}
> Which seems to be a reusable buffer for serializing/deserializing the data.
> From my understating there is a copy of this variable created per each
> instance of ProtobufTranslationImpl. However the proper use of ThreadLocal it
> seems to be one instance per thread and the current implementation seems to
> be missing the `static` keyword when defining the thread local variable:
> See [https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html]
> {code:java}
> ThreadLocal instances are typically private static fields in classes that
> wish to associate state with a thread (e.g., a user ID or Transaction ID).
> {code}
> See the attached snapshot from a memory dump we took.
--
This message was sent by Atlassian Jira
(v8.3.2#803003)