Matt Bishop created OPENJPA-2529:
------------------------------------
Summary: QueryImpl.toClass() causes thousands of BLOCKED threads
under load
Key: OPENJPA-2529
URL: https://issues.apache.org/jira/browse/OPENJPA-2529
Project: OpenJPA
Issue Type: Improvement
Components: kernel
Affects Versions: 2.3.0
Environment: Java7u51 Linux (Centos?)
Reporter: Matt Bishop
Priority: Minor
When evaluating a JPQL query, the kernel eventually finds it's way to
QueryImpl.toClass(string) to provide a Class for a given name. This delegates
to Serp, which calls Class.forName(string) (I'm leaving out the class loaders
here).
The problem is that under heavy load, with lots of threads, the calls to
Class.forName() BLOCK as that method has to get the class loader lock before
finding/loading the named class. In my perf tests, which executes 500 threads
against JPA and takes a thread dump every 10 seconds, I find about 2400 BLOCKED
threads at exactly the same place:
"http-bio-8080-exec-16" daemon prio=10 tid=0x00007fc95c503000 nid=0x2eb waiting
for monitor entry [0x00007fc98034d000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:270)
at serp.util.Strings.toClass(Strings.java:162)
at serp.util.Strings.toClass(Strings.java:108)
at org.apache.openjpa.kernel.QueryImpl.toClass(QueryImpl.java:1698)
at org.apache.openjpa.kernel.QueryImpl.classForName(QueryImpl.java:1653)
at
org.apache.openjpa.kernel.ExpressionStoreQuery$1.classForName(ExpressionStoreQuery.java:113)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getPathOrConstant(JPQLExpressionBuilder.java:1883)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.eval(JPQLExpressionBuilder.java:1189)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getValue(JPQLExpressionBuilder.java:2095)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getValue(JPQLExpressionBuilder.java:2081)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.eval(JPQLExpressionBuilder.java:1137)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getExpression(JPQLExpressionBuilder.java:2011)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.eval(JPQLExpressionBuilder.java:1059)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getExpression(JPQLExpressionBuilder.java:2011)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.eval(JPQLExpressionBuilder.java:1001)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.evalWhereClause(JPQLExpressionBuilder.java:672)
at
org.apache.openjpa.kernel.jpql.JPQLExpressionBuilder.getQueryExpressions(JPQLExpressionBuilder.java:297)
at org.apache.openjpa.kernel.jpql.JPQLParser.eval(JPQLParser.java:67)
at
org.apache.openjpa.kernel.ExpressionStoreQuery$DataStoreExecutor.<init>(ExpressionStoreQuery.java:763)
at
org.apache.openjpa.kernel.ExpressionStoreQuery.newDataStoreExecutor(ExpressionStoreQuery.java:179)
at
org.apache.openjpa.datacache.QueryCacheStoreQuery.newDataStoreExecutor(QueryCacheStoreQuery.java:288)
at
org.apache.openjpa.kernel.QueryImpl.createExecutor(QueryImpl.java:752)
at
org.apache.openjpa.kernel.QueryImpl.compileForDataStore(QueryImpl.java:710)
at
org.apache.openjpa.kernel.QueryImpl.compileForExecutor(QueryImpl.java:692)
at org.apache.openjpa.kernel.QueryImpl.compile(QueryImpl.java:592)
at
org.apache.openjpa.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:997)
at
org.apache.openjpa.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:979)
My suggestion is to keep a map at QueryImpl from name to Class and avoid
Class.forName() when the class has already been found once.
Three issues to consider:
1. Classloader. Class caches usually are a bad idea because one can get a
different Class instance from a different class loader. In QueryImpl, only one
ClassLoader (_loader) is ever used.
2. Concurrency. The class cache needs to be thread-safe, so I used a
ConcurrentHashMap (JDK version) to store results. I'm ok with multiple threads
reloading the same class occasionally as they will simply replace the stored
class with the same class. That is preferable to locking the thread so only one
mapping can be created ever.
3. Class unload. The Class itself may be unloaded, but cannot be
garbage-collected because the map will retain a reference. I don't think this
is a real problem given the nature of the Query; the same JQPL tends to be
executed repeatedly, so it is unlikely that the class will be unloaded. I'm
sure someone has a different opinion on this.
The provided patch, which stores found classes in a map, has reduced the amount
of BLOCKED state threads in my test down to 100.
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)