By Linxing
• Performance
The JDK 11 G1 collector has significantly improved its GC performance compared to JDK 8. Both performance metrics and memory usage have seen substantial enhancements. Various industry data indicators also demonstrate that the JDK 11 G1 collector performs exceptionally well in response to burst traffic spikes.
|
Average TGC pause time |
Number of YGCs |
Number of Major GCs |
Total pause time |
Throughput |
JDK8+CMS |
311ms |
140 |
1 |
43.9s |
92.7% |
JDK11+CMS |
274ms |
122 |
1 |
34.9s |
94.2% |
JDK11+G1 |
177ms |
175 |
1 |
31.6s |
94.7% |
• Version compatibility
Spring Boot 2.7.x and later versions will no longer support Java 8 as the minimum version. Spring Boot 2.6.x is the last main-line version that officially supports Java 8. Some new middleware and components also no longer support JDK 8.
• Inevitable trend
JDK 11 (LTS) has become mainstream within the industry and is widely accepted and used in the Java development community and industry.
• JDK 11 has introduced significant changes and is not backward compatible. Therefore, the more complex your business code and the more extensive the chains that are called, the more challenging the upgrade will be. You will encounter many compatibility issues, such as the second-party package does not support the new version of JDK.
• JDK 11 has removed some APIs that have been marked as obsolete in Java 8, such as sun.misc.Unsafe methods, so your upgrade may also involve code changes.
• Verification is a long and time-consuming process, and many issues may only appear during runtime, so you need to validate the overall system functionality to ensure system stability.
• Download JDK 11 locally
It will not be discussed in detail here. Pay attention to distinguishing between the arm version and the x64 version of JDK.
• Select JDK 11 on IDEA
• Upgrade the framework
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<java.version>11</java.version>
<spring-boot.version>2.1.6.RELEASE</spring-boot.version>
<lombok.version>1.18.12</lombok.version>
Software |
Minimum version |
spring-boot |
2.1.x begins to provide support for JDK 11 |
spring |
5.1.x |
idea |
2018.2 |
maven |
3.5.0 |
lombok |
1.18.x |
netty |
Upgrade it to version 4.1.33.Final or later, otherwise, it will cause off-heap memory usage. |
apache common lang3 |
3.12.0 |
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-ri</artifactId>
<version>2.3.3</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.jvm</groupId>
<artifactId>java-migration-jdk-patch</artifactId>
<version>0.3.1</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
</dependency>
• Existing issues
Deprecated: A global security auto-configuration is now provided
In Spring Boot 2.0 and above versions, this configuration item has been deprecated and removed. If you want to disable endpoint security, you need to configure the Actuator endpoints in the Spring Security configuration. This configuration item enables security detection by default.
You need to specify a version number.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.4.Final</version>
</dependency>
Cause
In JDK 8, the JDWP (Java Debug Wire Protocol) bound host/IP to 0.0.0.0 by default. For security reasons, this was changed to localhost (127.0.0.1) in JDK 9 and later versions. Therefore, if developers only specify the port in the debug configuration, remote debugging can't run after the upgrade.
Solution
When specifying debug options, set the host/IP to *, for example:
agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000
Or 0.0.0.0, such as:
agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8000
(1) maven-compiler-plugin
: It is recommended to upgrade this plugin to the latest version. Additionally, specify the version in the parent POM and in the POM of each module where you need to ensure a specific version (such as a JDK 8 version package meant for others). Use the following configuration:
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target>
(2) Spring Boot and Spring versions: Spring supports JDK 11 since version 5.1, and Spring Boot supports JDK 11 since 2.1.X. We recommend you upgrade to the latest available versions.
(3) Netty should be upgraded to version 4.1.33 or above due to issues with the release of off-heap memory.
(4) Since Lombok inserts its own compilation logic at compile time, after upgrading to JDK 11, you need to upgrade Lombok to the latest version (the latest version at the time of writing is 1.18.24).
(5) Most applications may need to upgrade Spring or Spring Boot. Please ensure that thorough regression testing is performed.
(6) The security-spring-boot-starter
version is 1.x. x or 2.x. x, corresponding to Spring Boot1 or Spring Boot2. Please upgrade to 2.x. x.
• Use the G1 garbage collector
Remove
#SERVICE_OPTS="${SERVICE_OPTS} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5000"
#SERVICE_OPTS="${SERVICE_OPTS} -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly"
#SERVICE_OPTS="${SERVICE_OPTS} -XX:+ExplicitGCInvokesConcurrent -Dsun.rmi.dgc.server.gcInterval=2592000000 -Dsun.rmi.dgc.client.gcInterval=2592000000"
#SERVICE_OPTS="${SERVICE_OPTS} -XX:ParallelGCThreads=4"
#SERVICE_OPTS="${SERVICE_OPTS} -Xloggc:${MIDDLEWARE_LOGS}/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
SERVICE_OPTS="${SERVICE_OPTS} -XX:+UseG1GC -XX:+UseVtableBasedCHA -XX:+UseCompactObjectHeaders"
SERVICE_OPTS="${SERVICE_OPTS} -XX:G1HeapRegionSize=8m"
SERVICE_OPTS="${SERVICE_OPTS} -XX:+G1BarrierSkipDCQ"
SERVICE_OPTS="${SERVICE_OPTS} -Xlog:gc*:/home/admin/logs/gc.log:time"
SERVICE_OPTS="${SERVICE_OPTS} -XX:G1HeapWastePercent=2"
SERVICE_OPTS="${SERVICE_OPTS} -XX:+ExplicitGCInvokesConcurrent -Dsun.rmi.dgc.server.gcInterval=2592000000 -Dsun.rmi.dgc.client.gcInterval=2592000000"
if [ -n "$AJDK_MAX_PROCESSORS_LIMIT" ]; then
SERVICE_OPTS="${SERVICE_OPTS} -XX:ActiveProcessorCount=$AJDK_MAX_PROCESSORS_LIMIT"
fi
Typically, the G1 GC is a garbage collector with defaults that enable it to work efficiently without modification. The old, standard Java GC tuning experiences do not apply.
-Xmn parameters generally require no additional configuration.
G1 has preset values for -XX:NewSize and -XX:MaxNewSize (they are not the same), and it will calculate and set the size of the young generation for each GC based on actual runtime conditions to control GC pauses.
-XX:NewRatio requires no additional configuration.
-XX:SurvivorRatio generally requires no additional configuration.
Generally, most users are not clear about the meaning of this parameter or its impact on GC. G1 adaptively processes the GC behavior related to this parameter.
-XX:MaxGCPauseMillis=N (A target value for desired maximum pause time in milliseconds.)
The default value is 200 ms. Many users tend to set it lower, but in most cases, this doesn't make much of a difference. The actual G1 GC pause tasks will not decrease with shorter pause times. In contrast, a smaller target value can lead to more frequent GCs, which will affect throughput. Generally, you do not need to set this parameter. For better throughput, it is usually set larger to ensure that the young generation does not decrease too much. You can also consult JVM experts to consider adjusting -XX:NewSize and -XX:MaxNewSize to maintain the size of the young generation and appropriate throughput performance.
-XX:InitiatingHeapOccupancyPercent=N -XX:-G1UseAdaptiveIHOP (Commonly used in e-commerce core applications.)
These two parameters are usually used together. JDK 11 has introduced G1UseAdaptiveIHOP to improve the utilization of the old generation (large heaps, typically several tens of GB or more than 100 GB). In medium-sized heaps (usually 5-20 GB) commonly used in our container specifications, we sometimes encounter issues that old GC or young GC occurs too frequently. Therefore, you need to set a suitable static IHOP (the heap occupancy threshold for the old generation to trigger a marking cycle).
-XX:G1HeapRegionSize (E-commerce core applications typically use 8 MB to 32 MB.)
This parameter sets the size of a G1 region to address GC exceptions caused by humongous objects (any object that is more than half a heap region size, occupying one or more regions). The default heap region size is Heap size/2048. If the default value is too small, excessive allocation of humongous objects can easily cause To-space exhausted pause time exceptions.
[2024-01-05T14:14:31.817+0800] GC(266) To-space exhausted
-XX:G1HeapWastePercent (The default value is 5, and some e-commerce core applications set it to 2.)
When collecting old regions, the mixed GC cycle will not be initiated when the reclaimable percentage is less than the 5% heap size to reduce the pause time of mixed GCs. For example, when Xmx is 10G and the heap waste percentage is 5%, 500 MB can be wasted, which is a waste for the Java heap. Therefore, you can reduce the heap space waste by setting this parameter to 2. It is not recommended to set it to 0, as this can significantly increase the pause time of mixed GCs.
-XX:G1MixedGCCountTarget (The target number of mixed GCs, and the default value is 8.)
The actual number of mixed GCs usually falls short of the G1MixedGCCountTarget. If the concurrent marking and mixed GC cycles are infrequent and the pause time of a single mixed GC is too long, you can typically consider increasing this parameter to 16, for example, to distribute the workload of a single mixed GC pause, thereby reducing the pause time.
After upgrading from CMS to G1, memory usage in containers and Java processes has increased
Many applications observe an overall increase in memory usage in containers and Java processes after upgrading to JDK 11, primarily due to differences in heap usage. The old generation in CMS is non-compacting and controlled by CMSInitiatingOccupancyFraction, which determines the occupancy percentage to trigger GC. Therefore, shortly after application startup, the heap's old generation usage does not rise quickly. In contrast, G1's heap regions are loosely managed, utilizing the entire heap, which makes memory usage appear higher. The essence of this issue is heap utilization. CMS initially reserves a portion of the heap that is not used. This issue can be solved by lowering the Xmx parameter (it is also used by some e-commerce core applications).
Pauce exceptions caused by To-space exhausted in GC logs
The majority are caused by the allocation of large objects, frequently appearing in GC logs.
Pause Young (Concurrent Start) (G1 Humongous Allocation)
Excessive allocation of large objects quickly fills up heap space, leading to To-space exhausted/evacuation failure in GC, which requires additional pause time to handle and even more time-consuming full GC for complete heap compaction.
Frequent GCs
For the traditional CMS/Parallel GC, the size of the young generation is fixed. However, the size of the young generation in G1 is automatically adjusted. To meet pause time requirements, the young generation size might be reduced, leading to more frequent GCs. Generally, it's recommended to avoid a too-small MaxGCPauseMillis parameter as the description mentioned above. You can also increase the configuration of the MaxGCPauseMillis, and consult a Q&A expert if necessary to adjust -XX:NewSize and -XX:MaxNewSize.
Excessively long mixed GC pauses
In addition to the young GC which collects and cleans up young generation objects, G1 also performs mixed GCs after the concurrent mark, which includes collecting old generation objects. Therefore, mixed GC usually has a longer pause time. If the pause time of a single mixed GC is too long, consider increasing the G1MixedGCCountTarget parameter mentioned above to further distribute the old generation object compaction tasks and reduce the pause time.
It can be seen that in daily operation, the G1 GC time consumption has been greatly reduced.
TPS20 under the same stress testing conditions
It can be clearly seen that the GC time consumption has significantly reduced, with speeds increased by about 70%.
As can be seen from the figure, the time consumption of YGC is significantly reduced and the performance is nearly improved by 50%. This is attributed to the generational collection capability.
|
YGC average pause time |
Number of YGCs |
Effect |
JDK8+CMS |
7.4ms |
10347 |
|
JDK11+G1 |
3.74ms |
10649 |
Performance is improved by 49.5% |
String str = " i am lzc ";
boolean isblank = str.isBlank(); //Determine whether the string is blank
boolean isempty = str.isEmpty(); //Determine whether the string is empty
String result1 = str.strip(); //Remove leading and trailing whitespace
String result2 = str.stripTrailing(); //Remove trailing whitespace
String result3 = str.stripLeading(); //Remove leading whitespace
String copyStr = str.repeat(2); //Copy the string several time
long lineCount = str.lines().count(); //Count the number of rows
System.out.println(isblank); //Result:false
System.out.println(isempty); //Result:false
System.out.println(result1); //Result:i am lzc
System.out.println(result2); //Result: i am lzc
System.out.println(result3); //Result:i am lzc
System.out.println(copyStr); //Result: i am lzc i am lzc
System.out.println(lineCount); //Result:1
Path filePath = Files.writeString(Path.of("/temp/a.txt"), "Sample text");
String fileContent = Files.readString(filePath);
System.out.println(fileContent.equals("Sample text"));
//Stream allows accepting a null value and returns 0 when calculating count.
long count = Stream.ofNullable(null).count();
System.out.println(count); // 0
//Methods take a predicate to decide which elements to drop from the stream.
//Simple words: remove elements from the collection that meet the condition until they no longer do.
List list1 = Stream.of(1, 2, 3, 2, 1)
.dropWhile(n -> n < 3)
.collect(Collectors.toList());
System.out.println(list1); // [3, 2, 1]
//Methods take a predicate to decide which elements to select from the stream.
//Simple words: extract elements from the collection that meet the condition until they no longer do.
List list2 = Stream.of(1, 2, 3, 2, 1)
.takeWhile(n -> n < 3)
.collect(Collectors.toList());
System.out.println(list2); // [1, 2]
List list1 = List.of(1, 3, 5, 7);
List list2 = List.copyOf(list1);
System.out.println(list2); //Result: [1,3,5,7]
Map<Integer, String> map1 = Map.of(1, "a", 2, "b", 3, "c");
Map<Integer, String> map2 = Map.copyOf(map1);
System.out.println(map2); //Result: {1=a, 2=b, 3=c}
//New method orElseThrow throws an exception if the value is absent.
Object v2 = Optional.ofNullable(null).orElseThrow(); //Result: An exception is thrown.
//New method ifPresentOrElse executes the first callback function if not null, and executes the second if null.
Optional.ofNullable(null).ifPresentOrElse(
(x) -> {
System.out.println("Data:" + x);
}, () -> {
System.out.println("Data is not found");
});
//Provide another Optionals as a callback for empty Optionals.
Object v3 = Optional.ofNullable(null)
.or(() -> Optional.of("fallback"))
.get(); //Result:fallback
System.out.println(v3);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
// Asynchronous
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
// Synchronous
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.
Alibaba Cloud Collaborates with Shiseido China on AI-driven DRUNKGPT
Java Thread Pool Implementation and Best Practices in Business Applications
1,076 posts | 263 followers
FollowOpenAnolis - September 5, 2022
Aliware - April 10, 2020
Alibaba Cloud Community - March 16, 2023
Alibaba Cloud Community - October 23, 2024
Alibaba Clouder - October 21, 2020
Alibaba Cloud Native Community - March 25, 2019
1,076 posts | 263 followers
FollowExplore Web Hosting solutions that can power your personal website or empower your online business.
Learn MoreA low-code development platform to make work easier
Learn MoreExplore how our Web Hosting solutions help small and medium sized companies power their websites and online businesses.
Learn MoreHelp enterprises build high-quality, stable mobile apps
Learn MoreMore Posts by Alibaba Cloud Community