[
https://issues.apache.org/jira/browse/OPENJPA-2529?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14137782#comment-14137782
]
Matt Bishop commented on OPENJPA-2529:
--------------------------------------
I can't find any way of submitting a patch file, but it's not big:
diff --git
a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryImpl.java
b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryImpl.java
index 1478c3a..e5ebf4b 100644
--- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryImpl.java
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/QueryImpl.java
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.collections.map.LinkedMap;
@@ -81,6 +82,7 @@ public class QueryImpl
implements Query {
private static final Localizer _loc =
Localizer.forPackage(QueryImpl.class);
+ private static final Map<String, Class> _classCache = new
ConcurrentHashMap<String, Class>();
private final String _language;
private final StoreQuery _storeQuery;
@@ -1687,8 +1689,16 @@ public class QueryImpl
if (_loader == null)
_loader = _broker.getConfiguration().getClassResolverInstance().
getClassLoader(_class, _broker.getClassLoader());
+ Class cachedClass = _classCache.get(name);
+ if (cachedClass != null) {
+ return cachedClass;
+ }
try {
- return Strings.toClass(name, _loader);
+ Class loadedClass = Strings.toClass(name, _loader);
+ if (loadedClass != null) {
+ _classCache.put(name, loadedClass);
+ }
+ return loadedClass;
} catch (RuntimeException re) {
} catch (NoClassDefFoundError ncdfe) {
}
> 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
> Labels: query-parser
>
> 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)