JVM common performance tuning

Common Tuning Methods

(1) Thread pool: Solve the problem of long user response time
(2) Connection pool
(3) JVM startup parameters: Adjust the memory ratio and garbage collection algorithm of each generation to improve throughput
(4) Program algorithm: Improve program logic algorithm to improve performance

1. Java thread pool (java.util.concurrent.ThreadPoolExecutor)

Most of the thread pools used by applications on JVM6 are the thread pools that come with the JDK. The reason why the mature Java thread pool is described is that the behavior of the thread pool is a bit different from what we imagined. The Java thread pool has several important configuration parameters:

corePoolSize: the number of core threads (the latest number of threads)
maximumPoolSize: the maximum number of threads, tasks exceeding this number will be rejected, users can customize the processing method through the RejectedExecutionHandler interface
keepAliveTime: the time that the thread remains active
workQueue: the work queue, which stores the executed tasks
The Java thread pool needs to pass in a Queue parameter (workQueue) to store the executed tasks, and the thread pool has completely different behaviors for different choices of Queue:

SynchronousQueue: A waiting queue with no capacity. The insert operation of one thread must wait for the remove operation of another thread. Using this Queue thread pool will allocate a new thread for each task.
LinkedBlockingQueue: Unbounded queue, using this Queue, the thread pool will Ignore the maximumPoolSize parameter, only use the corePoolSize thread to process all tasks, and the unprocessed tasks are queued in the
LinkedBlockingQueue ArrayBlockingQueue: Bounded queue, under the action of bounded queue and maximumPoolSize, the program will be difficult to tune: larger Queue and small maximumPoolSize will lead to low CPU load; small Queue and large pool, Queue will not start its due role.
In fact, our requirements are very simple. We hope that the thread pool can be the same as the connection pool. The minimum number of threads and the maximum number of threads can be set. When the minimum number < task < maximum number, new threads should be allocated for processing; when task > maximum number, It should wait for an idle thread to process the task.

However, the design idea of ​​the thread pool is that the task should be placed in the Queue. When the Queue cannot be placed, consider using a new thread for processing. If the Queue is full and new threads cannot be derived, the task will be rejected. The design leads to "put first and wait for execution", "do not let go before executing", "refuse to wait". Therefore, according to different Queue parameters, the maximumPoolSize cannot be increased blindly to improve the throughput.

Of course, to achieve our goal, we must encapsulate the thread pool. Fortunately, there are enough custom interfaces in ThreadPoolExecutor to help us achieve our goal. The way we encapsulate is:

Use SynchronousQueue as a parameter to make maximumPoolSize work to prevent threads from being allocated without limit. At the same time, you can improve system throughput by increasing maximumPoolSize
. Customize a RejectedExecutionHandler to process when the number of threads exceeds maximumPoolSize, and the processing method is to check at intervals Whether the thread pool can execute new tasks, and if the rejected tasks can be put back into the thread pool, the check time depends on the size of keepAliveTime.
2. Connection pool (org.apache.commons.dbcp.BasicDataSource)

When using org.apache.commons.dbcp.BasicDataSource, because the default configuration was used before, when the traffic volume was large, it was observed through JMX that many Tomcat threads were blocked on the lock of the Apache ObjectPool used by BasicDataSource. The direct reason was at that time. This is because the maximum number of connections in the BasicDataSource connection pool is set too small. The default BasicDataSource configuration uses only 8 maximum connections.

I also observed a problem that when the system is not accessed for a long time, such as 2 days, Mysql on the DB will disconnect all connections, resulting in unusable connections cached in the connection pool. In order to solve these problems, we fully studied BasicDataSource and found some optimization points:

Mysql supports 100 connections by default, so the configuration of each connection pool should be based on the number of machines in the cluster. If there are 2 servers, each can be set to 60
initialSize: The parameter is the number of connections that have been opened all the
time minEvictableIdleTimeMillis: This parameter sets each The idle time of a connection, after which the connection will be closed
timeBetweenEvictionRunsMillis: the running cycle of the background thread, used to detect expired connections
maxActive: the maximum number of connections that can be
allocated , the connection will be closed directly. Only connections with initialSize < x < maxIdle will be periodically checked for expiration. This parameter is mainly used to improve throughput during peak access.
How is initialSize maintained? After researching the code, it is found that BasicDataSource will close all overdue connections, and then open the initialSize number of connections. This feature, together with minEvictableIdleTimeMillis and timeBetweenEvictionRunsMillis, ensures that all overdue initialSize connections will be reconnected, thus preventing Mysql from breaking if there is no action for a long time. connection drop problem.
3. JVM parameters
In the JVM startup parameters, you can set some parameter settings related to memory and garbage collection. By default, the JVM will work well without any settings, but it must be carefully configured for some well-configured servers and specific applications. Tuning for best performance. By setting we hope to achieve some goals:
the GC time is enough,
the number of small GCs is low enough, the cycle
of full GC is long enough
The first two are currently contradictory. If the GC time is to be small, a smaller heap must be obtained. To ensure that the number of GCs is small enough, a larger heap must be guaranteed. We can only take a balance.

(1) For the general settings of the JVM heap, the minimum and maximum values ​​can be limited by -Xms -Xmx. In order to prevent the garbage collector from shrinking the heap between the minimum and maximum values, we usually set the maximum and minimum values ​​to The same value
(2) the young generation and the old generation will allocate heap memory according to the default ratio (1:2). You can adjust the size between the two by adjusting the ratio NewRadio between the two, or for the collection generation. , such as the young generation, set its absolute size through -XX:newSize -XX:MaxNewSize. Similarly, in order to prevent the heap shrinkage of the young generation, we usually set -XX:newSize -XX:MaxNewSize to the same size
(3) How big are the young and old generation settings reasonable? There is no doubt that this question of mine has no answer, otherwise there would be no tuning. Let's look at the impact of the change in the size of the two

Larger young generation will inevitably lead to smaller old generation, large young generation will prolong the normal GC cycle, but will increase the time of each GC; small old generation will lead to more frequent Full GC
and smaller young generation Generation will inevitably lead to larger old generation, small young generation will lead to frequent GC, but each GC time will be shorter; large old generation will reduce the frequency of Full GC
How to choose should depend on the application object life cycle The distribution of : if the application has a large number of temporary objects, a larger young generation should be selected; if there are relatively many persistent objects, the old generation should be appropriately increased. However, many applications do not have such obvious features. The following two points should be considered when making a decision: (A) Based on the principle of as few Full GC as possible, let the old generation cache common objects as much as possible. The default ratio of JVM is 1:2. (B) By observing the application for a period of time to see how much memory the old generation will occupy at the peak, increase the young generation according to the actual situation without affecting the Full GC. For example, the ratio can be controlled at 1:1. However, at least 1/3 of the growth space should be reserved for the old generation
(4) On machines with better configuration (such as multi-core, large memory), a parallel collection algorithm can be selected for the old generation: -XX:+UseParallelOldGC , the default Collect (5) Thread stack settings for Serial
: Each thread will open a 1M stack by default, which is used to store stack frames, call parameters, local variables, etc. For most applications, this default value is too much, generally 256K is enough use. In theory, in the case of constant memory, reducing the stack of each thread can generate more threads, but this is actually limited by the operating system.
(4) Heap Dump information can be typed through the following parameters
-XX:HeapDumpPath
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:/usr/aaa/dump/heap_trace.txt
3. Improve program logic algorithm to improve performance

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326693105&siteId=291194637
Recommended