Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Class loading problems caused by parallel flow in Tomcat applications

2025-02-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)06/02 Report--

This article was first posted on the official account of Wechat, vivo Internet technology.

Link: https://mp.weixin.qq.com/s/f-X3n9cvDyU5f5NYH6mhxQ

Authors: Xiao Mingxuan, Wang Daohuan

With the increasing popularity of Java8, more and more developers use the feature of parallel flow (parallel) to improve code execution efficiency. However, the author finds that using parallel flow in Tomcat container will fail to load classes dynamically. By comparing the source code of several versions of Tomcat, combining the principle of parallel flow and JVM class loading mechanism, we successfully locate the source of the problem. This paper analyzes this problem and gives a solution.

I. problem scenario

In an application, Dubbo is called through parallel flow when the service is started. The calling code is as follows:

Lists.partition (ids, BATCH_QUERY_LIMIT) .stream () .parallel () .map (Req::new) .map (client::batchQuery) .parallel (Collectors.toList ())

A large number of WARN logs com.alibaba.com.caucho.hessian.io.SerializerFactory.getDeserializer Hessian/Burlap:'XXXXXXX' is an unknown class in null:java.lang.ClassNotFoundException: XXXXXXX are found in the call log, and the error java.lang.ClassCastException: java.util.HashMap cannot be cast to XXXXXXX is thrown when the result is returned using the API.

Second, cause analysis 1. Preliminary positioning

First of all, according to the error log, you can see that because the entity class of the parameter returned by the dependent Dubbo service cannot be found, the data message returned by Dubbo cannot be converted into the corresponding entity during deserialization, and java.lang.ClassCastException is reported in type casting. Locate the problematic class as com.alibaba.com.caucho.hessian.io.SerializerFactory by checking the thread stack and WARN log. The class cannot be loaded because _ loader is null. The related code is as follows:

Try {Class cl = Class.forName (type, false, _ loader); deserializer = getDeserializer (cl);} catch (Exception e) {log.warning ("Hessian/Burlap:'" + type + "'is an unknown class in" + ":\ n" + e); log.log (Level.FINER, e.toString (), e);}

Next, go up and locate why _ loader initializes _ loader in the null,SerializerFactory constructor. The initialization code is as follows, and you can see that _ loader uses the contextClassLoader of the current thread.

Public SerializerFactory () {this (Thread.currentThread (). GetContextClassLoader ());} public SerializerFactory (ClassLoader loader) {_ loader = loader;}

According to the stack, you can see that the current thread is ForkJoinWorkerThread,ForkJoinWorkerThread, which is a worker thread within the Fork/Join framework (Fork/Join is used by Java8 parallel flow). The JDK document states that:

The context ClassLoader is provided by the creator of the thread for use by code running in this thread when loading classes and resources. If not set, the default is the ClassLoader context of the parent Thread.

So the current thread contextClassLoader should be consistent with the parent thread that created this thread, not null?

Continue to look at the source code created by ForkJoinWorkerThread, first create a thread using ForkJoinWorkerThreadFactory, and then register the created thread in ForkJoinPool. The logic of thread initialization is no different from that of ordinary threads. It is found that it is difficult to find problems alone from JDK itself, so the analysis is transferred to Tomcat.

2. Problems caused by Tomcat upgrade

Some versions of Tomcat7.0.x are tested and compared, and it is found that there is no this problem in the version before 7.0.74, but there is a similar problem in the version after 7.0.74. The experimental results are shown in the table below.

At this point, the problem is caused by the version of Tomcat. Through source code comparison, it is found that there is more such code in Tomcat after version 7.0.74:

If (forkJoinCommonPoolProtection & & IS_JAVA_8_OR_LATER) {/ / Don't override any explicitly set property if (System.getProperty (FORK_JOIN_POOL_THREAD_FACTORY_PROPERTY) = = null) {System.setProperty (FORK_JOIN_POOL_THREAD_FACTORY_PROPERTY, "org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory");}} private static class SafeForkJoinWorkerThread extends ForkJoinWorkerThread {protected SafeForkJoinWorkerThread (ForkJoinPool pool) {super (pool) SetContextClassLoader (ForkJoinPool.class.getClassLoader ());}}

In the Java8 environment, Tomcat after version 7.0.74 defaults to SafeForkJoinWorkerThreadFactory as the creation factory of ForkJoinWorkerThread, and sets the contextClassLoader of the thread to ForkJoinPool.class.getClassLoader (). ForkJoinPool is a class belonging to the rt.jar package and loaded by BootStrap ClassLoader, so the corresponding class loader is null. At this point, the question that _ loader is empty is clear, but why would Tomcat bother to use null as the contextClassLoader of this ForkJoinWorkerThread?

Continue to compare Tomcat's changeLog http://tomcat.apache.org/tomcat-7.0-doc/changelog.html and find that Tomcat fixes the memory leak problem Bug 60620-[JRE] Memory leak found in java.util.concurrent.ForkJoinPool caused by ForkJoinPool in this version. Why does a thread's contextClassLoader cause a memory leak?

3. The riddle of contextClassLoader memory leak

After JDK1.2, the parent delegation model of classloaders has been widely introduced. Its working process is that if a class loader receives a request for class loading, it will not attempt to load the class itself at first, but delegate the entire request to the parent class loader to complete, so all load requests should eventually be passed to the top-level startup class loader, only when the parent loader reports that it cannot complete the load request. The child loader will try to load it on its own, as shown in the following figure.

However, the model of parental delegation does not guarantee the process of loading classes in an application. A typical example is JNDI services, which are defined in rt.jar and implemented by third parties, and Bootstrap ClassLoader obviously does not recognize the code. In order to solve this problem, JDK1.2 also introduces a thread context class loader (Thread Context ClassLoader) to load the class as a supplement to the parent delegation model.

Going back to the issue of memory leaks, imagine a scenario where if a thread holds ClassLoaderA (several classes are loaded by ClassLoaderA), thread A still holds a reference to ClassLoaderA after the application needs to unload ClassLoaderA and the classes loaded by ClassLoaderA, but the business side thinks that these classes and loaders have been unloaded cleanly, because the class loader and its loaded classes have two-way references. As a result, the class loader and its loaded classes cannot be garbage collected, resulting in memory leaks. In parallel flow, ForkJoinPool and ForkJoinWorkerThreadFactory are static and shared by default (JDK officially recommends that the creation thread itself is an opposite operation to avoid resource waste caused by repeated creation of ForkJoinWorkerThread). The following figure describes the scenario where memory leakage occurs:

Therefore, Tomcat defaults to using SafeForkJoinWorkerThreadFactory as the ForkJoinWorkerThreadFactory, and specifies that the contextClassLoader of the ForkJoinWorkerThread created by the factory is ForkJoinPool.class.getClassLoader (), instead of JDK's default contextClassLoader that inherits the parent thread, thus avoiding the classloader memory leak caused by parallel flows in Tomcat applications.

III. Summary

During development, if parallel flows are used in compute-intensive tasks, avoid dynamically loading classes in subtasks; try to use thread pools instead of parallel flows in other business scenarios. In short, we need to avoid dynamic loading of custom classes or third-party classes through parallel streams in Tomcat applications.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Servers

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report