国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

做網(wǎng)站的公司經(jīng)營范圍怎么寫微信小程序怎么做

做網(wǎng)站的公司經(jīng)營范圍怎么寫,微信小程序怎么做,商務(wù)網(wǎng)站教程,保定建設(shè)網(wǎng)站1 GraalVM 1.1 什么是GraalVM GraalVM是Oracle官方推出的一款高性能JDK,使用它享受比OpenJDK或者OracleJDK更好的性能。 GraalVM的官方網(wǎng)址:https://www.graalvm.org/ 官方標(biāo)語:Build faster, smaller, leaner applications。 更低的CPU…

1 GraalVM

1.1 什么是GraalVM

GraalVM是Oracle官方推出的一款高性能JDK,使用它享受比OpenJDK或者OracleJDK更好的性能。

GraalVM的官方網(wǎng)址:https://www.graalvm.org/

官方標(biāo)語:Build faster, smaller, leaner applications。

更低的CPU、內(nèi)存使用率

官方標(biāo)語:Build faster, smaller, leaner applications。

  • 更低的CPU、內(nèi)存使用率

  • 更快的啟動(dòng)速度,無需預(yù)熱即可獲得最好的性能

  • 更好的安全性、更小的可執(zhí)行文件

  • 支持多種框架Spring Boot、Micronaut、Helidon 和 Quarkus。

  • 多家云平臺(tái)支持。

  • 通過Truffle框架運(yùn)行JS、Python、Ruby等其他語言。

GraalVM分為社區(qū)版(Community Edition)和企業(yè)版(Enterprise Edition)。企業(yè)版相比較社區(qū)版,在性能上有更多的優(yōu)化。

特性描述社區(qū)版企業(yè)版
收費(fèi)是否收費(fèi)免費(fèi)收費(fèi)
G1垃圾回收器使用G1垃圾回收器優(yōu)化垃圾回收性能×
Profile Guided Optimization(PGO)運(yùn)行過程中收集動(dòng)態(tài)數(shù)據(jù),進(jìn)一步優(yōu)化本地鏡像的性能×
高級(jí)優(yōu)化特性更多優(yōu)化技術(shù),降低內(nèi)存和垃圾回收的開銷×
高級(jí)優(yōu)化參數(shù)更多的高級(jí)優(yōu)化參數(shù)可以設(shè)置×

需求:

搭建Linux下的GraalVM社區(qū)版本環(huán)境。

步驟:

1、使用arch查看Linux架構(gòu)

2、根據(jù)架構(gòu)下載社區(qū)版的GraalVM:https://www.graalvm.org/downloads/

3、安裝GraalVM,安裝方式與安裝JDK相同

解壓文件:

設(shè)置環(huán)境變量:

4、使用java -version和HelloWorld測試GraalVM。

1.2 GraalVM的兩種運(yùn)行模式

  • JIT( Just-In-Time )模式 ,即時(shí)編譯模式

  • AOT(Ahead-Of-Time)模式 ,提前編譯模式

JIT模式的處理方式與Oracle JDK類似,滿足兩個(gè)特點(diǎn):

Write Once,Run Anywhere -> 一次編寫,到處運(yùn)行。

預(yù)熱之后,通過內(nèi)置的Graal即時(shí)編譯器優(yōu)化熱點(diǎn)代碼,生成比Hotspot JIT更高性能的機(jī)器碼。

需求:

分別在JDK8 、 JDK21 、 GraalVM 21 Graal即時(shí)編譯器、GraalVM 21 不開啟Graal即時(shí)編譯器運(yùn)行Jmh性能測試用例,對(duì)比其性能。

步驟:

1、在代碼文件夾中找到GraalVM的案例代碼,將java-simple-stream-benchmark文件夾下的代碼使用maven打包成jar包。

2、將jar包上傳到服務(wù)器,使用不同的JDK進(jìn)行測試,對(duì)比結(jié)果。

注意:

-XX:-UseJVMCICompiler參數(shù)可以關(guān)閉GraalVM中的Graal編譯器。

GraalVM開啟Graal編譯器下的性能還是不錯(cuò)的:

AOT(Ahead-Of-Time)模式 ,提前編譯模式

AOT 編譯器通過源代碼,為特定平臺(tái)創(chuàng)建可執(zhí)行文件。比如,在Windows下編譯完成之后,會(huì)生成exe文件。通過這種方式,達(dá)到啟動(dòng)之后獲得最高性能的目的。但是不具備跨平臺(tái)特性,不同平臺(tái)使用需要單獨(dú)編譯。

這種模式生成的文件稱之為Native Image本地鏡像。

需求: 使用GraalVM AOT模式制作本地鏡像并運(yùn)行。

步驟: 1、安裝Linux環(huán)境本地鏡像制作需要的依賴庫:

https://www.graalvm.org/latest/reference-manual/native-image/#prerequisites

2、使用 native-image 類名 制作本地鏡像。

3、運(yùn)行本地鏡像可執(zhí)行文件。

社區(qū)版的GraalVM使用本地鏡像模式性能不如Hotspot JVM的JIT模式,但是企業(yè)版的性能相對(duì)會(huì)高很多。

1.3 應(yīng)用場景

GraalVM的AOT模式雖然在啟動(dòng)速度、內(nèi)存和CPU開銷上非常有優(yōu)勢,但是使用這種技術(shù)會(huì)帶來幾個(gè)問題:

1、跨平臺(tái)問題,在不同平臺(tái)下運(yùn)行需要編譯多次。編譯平臺(tái)的依賴庫等環(huán)境要與運(yùn)行平臺(tái)保持一致。

2、使用框架之后,編譯本地鏡像的時(shí)間比較長,同時(shí)也需要消耗大量的CPU和內(nèi)存。

3、AOT 編譯器在編譯時(shí),需要知道運(yùn)行時(shí)所有可訪問的所有類。但是Java中有一些技術(shù)可以在運(yùn)行時(shí)創(chuàng)建類,例如反射、動(dòng)態(tài)代理等。這些技術(shù)在很多框架比如Spring中大量使用,所以框架需要對(duì)AOT編譯器進(jìn)行適配解決類似的問題。

解決方案:

1、使用公有云的Docker等容器化平臺(tái)進(jìn)行在線編譯,確保編譯環(huán)境和運(yùn)行環(huán)境是一致的,同時(shí)解決了編譯資源問題。

2、使用SpringBoot3等整合了GraalVM AOT模式的框架版本。

1.3.1 SpringBoot搭建GraalVM應(yīng)用

需求:

SpringBoot3對(duì)GraalVM進(jìn)行了完整的適配,所以編寫GraalVM服務(wù)推薦使用SpringBoot3。

步驟:

1、使用 https://start.spring.io/ spring提供的在線生成器構(gòu)建項(xiàng)目。

2、編寫業(yè)務(wù)代碼,修改原代碼將PostConstructor注解去掉:

@Service
public class UserServiceImpl implements UserService, InitializingBean {private List<User> users = new ArrayList<>();@Autowiredprivate UserDao userDao;@Overridepublic List<UserDetails> getUserDetails() {return userDao.findUsers();}@Overridepublic List<User> getUsers() {return users;}@Overridepublic void afterPropertiesSet() throws Exception {//初始化時(shí)生成數(shù)據(jù)for (int i = 1; i <= 10; i++) {users.add(new User((long) i, RandomStringUtils.randomAlphabetic(10)));}}
}

3、執(zhí)行 mvn -Pnative clean native:compile 命令生成本地鏡像。

4、運(yùn)行本地鏡像。

什么場景下需要使用GraalVM呢?

1、對(duì)性能要求比較高的場景,可以選擇使用收費(fèi)的企業(yè)版提升性能。

2、公有云的部分服務(wù)是按照CPU和內(nèi)存使用量進(jìn)行計(jì)費(fèi)的,使用GraalVM可以有效地降低費(fèi)用。

1.3.2 函數(shù)計(jì)算

傳統(tǒng)的系統(tǒng)架構(gòu)中,服務(wù)器等基礎(chǔ)設(shè)施的運(yùn)維、安全、高可用等工作都需要企業(yè)自行完成,存在兩個(gè)主要問題:

1、開銷大,包括了人力的開銷、機(jī)房建設(shè)的開銷。

2、資源浪費(fèi),面對(duì)一些突發(fā)的流量沖擊,比如秒殺等活動(dòng),必須提前規(guī)劃好容量準(zhǔn)備好大量的服務(wù)器,這些服務(wù)器在其他時(shí)候會(huì)處于閑置的狀態(tài),造成大量的浪費(fèi)。

Serverless架構(gòu)

隨著虛擬化技術(shù)、云原生技術(shù)的愈發(fā)成熟,云服務(wù)商提供了一套稱為Serverless無服務(wù)器化的架構(gòu)。企業(yè)無需進(jìn)行服務(wù)器的任何配置和部署,完全由云服務(wù)商提供。比較典型的有亞馬遜AWS、阿里云等。

Serverless架構(gòu)中第一種常見的服務(wù)是函數(shù)計(jì)算(Function as a Service),將一個(gè)應(yīng)用拆分成多個(gè)函數(shù),每個(gè)函數(shù)會(huì)以事件驅(qū)動(dòng)的方式觸發(fā)。典型代表有AWS的Lambda、阿里云的FC。

函數(shù)計(jì)算主要應(yīng)用場景有如下幾種:

① 小程序、API服務(wù)中的接口,此類接口的調(diào)用頻率不高,使用常規(guī)的服務(wù)器架構(gòu)容易產(chǎn)生資源浪費(fèi),使用Serverless就可以實(shí)現(xiàn)按需付費(fèi)降低成本,同時(shí)支持自動(dòng)伸縮能應(yīng)對(duì)流量的突發(fā)情況。

② 大規(guī)模任務(wù)的處理,比如音視頻文件轉(zhuǎn)碼、審核等,可以利用事件機(jī)制當(dāng)文件上傳之后,自動(dòng)觸發(fā)對(duì)應(yīng)的任務(wù)。

函數(shù)計(jì)算的計(jì)費(fèi)標(biāo)準(zhǔn)中包含CPU和內(nèi)存使用量,所以使用GraalVM AOT模式編譯出來的本地鏡像可以節(jié)省更多的成本。

步驟:

1、在項(xiàng)目中編寫Dockerfile文件。

# Using Oracle GraalVM for JDK 17
FROM container-registry.oracle.com/graalvm/native-image:17-ol8 AS builder# Set the working directory to /home/app
WORKDIR /build# Copy the source code into the image for building
COPY . /build
RUN chmod 777 ./mvnw# Build
RUN ./mvnw --no-transfer-progress native:compile -Pnative# The deployment Image
FROM container-registry.oracle.com/os/oraclelinux:8-slimEXPOSE 8080# Copy the native executable into the containers
COPY --from=builder /build/target/spring-boot-3-native-demo app
ENTRYPOINT ["/app"]

2、使用服務(wù)器制作鏡像,這一步會(huì)消耗大量的CPU和內(nèi)存資源,同時(shí)GraalVM相關(guān)的鏡像服務(wù)器在國外,建議使用阿里云的鏡像服務(wù)器制作Docker鏡像。

3、使用函數(shù)計(jì)算將Docker鏡像轉(zhuǎn)換成函數(shù)服務(wù)。

配置觸發(fā)器:

4、綁定域名并進(jìn)行測試。

需要準(zhǔn)備一個(gè)自己的域名:

配置接口路徑:

會(huì)出現(xiàn)一個(gè)錯(cuò)誤:

把域名導(dǎo)向阿里云的域名:

測試成功:

1.3.3 Serverless應(yīng)用

函數(shù)計(jì)算的服務(wù)資源比較受限,比如AWS的Lambda服務(wù)一般無法支持超過15分鐘的函數(shù)執(zhí)行,所以云服務(wù)商提供了另外一套方案:基于容器的Serverless應(yīng)用,無需手動(dòng)配置K8s中的Pod、Service等內(nèi)容,只需選擇鏡像就可自動(dòng)生成應(yīng)用服務(wù)。

同樣,Serverless應(yīng)用的計(jì)費(fèi)標(biāo)準(zhǔn)中包含CPU和內(nèi)存使用量,所以使用GraalVM AOT模式編譯出來的本地鏡像可以節(jié)省更多的成本。

服務(wù)分類交付模式彈性效率計(jì)費(fèi)模式
函數(shù)計(jì)算函數(shù)毫秒級(jí)

調(diào)用次數(shù)

CPU內(nèi)存使用量

Serverless應(yīng)用鏡像容器秒級(jí)CPU內(nèi)存使用量

步驟:

1、在項(xiàng)目中編寫Dockerfile文件。

2、使用服務(wù)器制作鏡像,這一步會(huì)消耗大量的CPU和內(nèi)存資源,同時(shí)GraalVM相關(guān)的鏡像服務(wù)器在國外,建議使用阿里云的鏡像服務(wù)器制作Docker鏡像。

前兩步同實(shí)戰(zhàn)案例2

3、配置Serverless應(yīng)用,選擇容器鏡像、CPU和內(nèi)存。

4、綁定外網(wǎng)負(fù)載均衡并使用Postman進(jìn)行測試。

先別急著點(diǎn)確定,需要先創(chuàng)建彈性公網(wǎng)IP:

全選默認(rèn),然后創(chuàng)建:

創(chuàng)建SLB負(fù)載均衡:

這次就可以成功創(chuàng)建了:

綁定剛才創(chuàng)建的SLB負(fù)載均衡:

訪問公網(wǎng)IP就能處理請(qǐng)求了:

1.4 參數(shù)優(yōu)化和故障診斷

由于GraalVM是一款獨(dú)立的JDK,所以大部分HotSpot中的虛擬機(jī)參數(shù)都不適用。常用的參數(shù)參考:官方手冊(cè)。

  • 社區(qū)版只能使用串行垃圾回收器(Serial?GC),使用串行垃圾回收器的默認(rèn)最大 Java 堆大小會(huì)設(shè)置為物理內(nèi)存大小的 80%,調(diào)整方式為使用 -Xmx最大堆大小。如果希望在編譯期就指定該大小,可以在編譯時(shí)添加參數(shù)-R:MaxHeapSize=最大堆大小。

  • G1垃圾回收器只能在企業(yè)版中使用,開啟方式為添加--gc=G1參數(shù),有效降低垃圾回收的延遲。

  • 另外提供一個(gè)Epsilon?GC,開啟方式:--gc=epsilon ,它不會(huì)產(chǎn)生任何的垃圾回收行為所以沒有額外的內(nèi)存、CPU開銷。如果在公有云上運(yùn)行的程序生命周期短暫不產(chǎn)生大量的對(duì)象,可以使用該垃圾回收器,以節(jié)省最大的資源。

-XX:+PrintGC -XX:+VerboseGC 參數(shù)打印垃圾回收詳細(xì)信息。

添加虛擬機(jī)參數(shù):

打印出了垃圾回收的信息:

1.4.1 實(shí)戰(zhàn)案例4:內(nèi)存快照文件的獲取

需求:

獲得運(yùn)行中的內(nèi)存快照文件,使用MAT進(jìn)行分析。

步驟:

1、編譯程序時(shí),添加 --enable-monitoring=heapdump,參數(shù)添加到pom文件的對(duì)應(yīng)插件中。

<plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId><configuration><buildArgs><arg>--enable-monitoring=heapdump,jfr</arg></buildArgs></configuration>
</plugin>

2、運(yùn)行中使用 kill -SIGUSR1 進(jìn)程ID 命令,創(chuàng)建內(nèi)存快照文件。

3、使用MAT分析內(nèi)存快照文件。

1.4.2 實(shí)戰(zhàn)案例5:運(yùn)行時(shí)數(shù)據(jù)的獲取

JDK Flight Recorder (JFR) 是一個(gè)內(nèi)置于 JVM 中的工具,可以收集正在運(yùn)行中的 Java 應(yīng)用程序的診斷和分析數(shù)據(jù),比如線程、異常等內(nèi)容。GraalVM本地鏡像也支持使用JFR生成運(yùn)行時(shí)數(shù)據(jù),導(dǎo)出的數(shù)據(jù)可以使用VisualVM分析。

步驟:

1、編譯程序時(shí),添加 --enable-monitoring=jfr,參數(shù)添加到pom文件的對(duì)應(yīng)插件中。

<plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId><configuration><buildArgs><arg>--enable-monitoring=heapdump,jfr</arg></buildArgs></configuration>
</plugin>

2、運(yùn)行程序,添加 -XX:StartFlightRecording=filename=recording.jfr,duration=10s參數(shù)。

3、使用VisualVM分析JFR記錄文件。

2 新一代的GC

2.1 垃圾回收器的技術(shù)演進(jìn)

不同的垃圾回收器設(shè)計(jì)的目標(biāo)是不同的,如下圖所示:

2.2 Shenandoah GC

Shenandoah 是由Red Hat開發(fā)的一款低延遲的垃圾收集器,Shenandoah 并發(fā)執(zhí)行大部分 GC 工作,包括并發(fā)的整理,堆大小對(duì)STW的時(shí)間基本沒有影響。

1、下載。Shenandoah只包含在OpenJDK中,默認(rèn)不包含在內(nèi)需要單獨(dú)構(gòu)建,可以直接下載構(gòu)建好的。 下載地址:https://builds.shipilev.net/openjdk-jdk-shenandoah/

選擇方式如下:

{aarch64, arm32-hflt, mipsel, mips64el, ppc64le, s390x, x86_32, x86_64}:架構(gòu),使用arch命令選擇對(duì)應(yīng)的的架構(gòu)。

{server,zero}:虛擬機(jī)類型,選擇server,包含所有GC的功能。

{release, fastdebug, Slowdebug, optimization}:不同的優(yōu)化級(jí)別,選擇release,性能最高。

{gcc*-glibc*, msvc*}:編譯器的版本,選擇較高的版本性能好一些,如果兼容性有問題(無法啟動(dòng)),選擇較低的版本。

2、配置。將OpenJDK配置到環(huán)境變量中,使用java –version進(jìn)行測試。打印出如下內(nèi)容代表成功。

3、添加參數(shù),運(yùn)行Java程序。

-XX:+UseShenandoahGC 開啟Shenandoah GC

-Xlog:gc 打印GC日志

/** Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.** This code is free software; you can redistribute it and/or modify it* under the terms of the GNU General Public License version 2 only, as* published by the Free Software Foundation.  Oracle designates this* particular file as subject to the "Classpath" exception as provided* by Oracle in the LICENSE file that accompanied this code.** This code is distributed in the hope that it will be useful, but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License* version 2 for more details (a copy is included in the LICENSE file that* accompanied this code).** You should have received a copy of the GNU General Public License version* 2 along with this work; if not, write to the Free Software Foundation,* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.** Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA* or visit www.oracle.com if you need additional information or have any* questions.*/package org.sample;import com.sun.management.OperatingSystemMXBean;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;//執(zhí)行5輪預(yù)熱,每次持續(xù)2秒
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
//輸出毫秒單位
@OutputTimeUnit(TimeUnit.MILLISECONDS)
//統(tǒng)計(jì)方法執(zhí)行的平均耗時(shí)
@BenchmarkMode(Mode.AverageTime)
//java -jar benchmarks.jar -rf json
@State(Scope.Benchmark)
public class MyBenchmark {//每次測試對(duì)象大小 4KB和4MB@Param({"4","4096"})int perSize;private void test(Blackhole blackhole){//每次循環(huán)創(chuàng)建堆內(nèi)存60%對(duì)象 JMX獲取到Java運(yùn)行中的實(shí)時(shí)數(shù)據(jù)MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();//獲取堆內(nèi)存大小MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();//獲取到剩余的堆內(nèi)存大小long heapSize = (long) ((heapMemoryUsage.getMax() - heapMemoryUsage.getUsed()) * 0.6);//計(jì)算循環(huán)次數(shù)long size = heapSize / (1024 * perSize);for (int i = 0; i < 4; i++) {List<byte[]> objects = new ArrayList<>((int)size);for (int j = 0; j < size; j++) {objects.add(new byte[1024 * perSize]);}blackhole.consume(objects);}}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseSerialGC"})public void serialGC(Blackhole blackhole){test(blackhole);}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseParallelGC"})public void parallelGC(Blackhole blackhole){test(blackhole);}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g"})public void g1(Blackhole blackhole){test(blackhole);}@Benchmark@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseShenandoahGC"})public void shenandoahGC(Blackhole blackhole){test(blackhole);}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).forks(1).build();new Runner(opt).run();}
}

測試結(jié)果:

Shenandoah GC對(duì)小對(duì)象的GC停頓很短,但是大對(duì)象效果不佳。

2.3 ZGC

ZGC 是一種可擴(kuò)展的低延遲垃圾回收器。ZGC 在垃圾回收過程中,STW的時(shí)間不會(huì)超過一毫秒,適合需要低延遲的應(yīng)用。支持幾百兆到16TB 的堆大小,堆大小對(duì)STW的時(shí)間基本沒有影響。

ZGC降低了停頓時(shí)間,能降低接口的最大耗時(shí),提升用戶體驗(yàn)。但是吞吐量不佳,所以如果Java服務(wù)比較關(guān)注QPS(每秒的查詢次數(shù))那么G1是比較不錯(cuò)的選擇。

ZGC版本更迭

ZGC的使用

OracleJDK和OpenJDK中都支持ZGC,阿里的DragonWell龍井JDK也支持ZGC但屬于其自行對(duì)OpenJDK 11的ZGC進(jìn)行優(yōu)化的版本。

建議使用JDK17之后的版本,延遲較低同時(shí)無需手動(dòng)配置并行線程數(shù)。

分代 ZGC添加如下參數(shù)啟用 -XX:+UseZGC -XX:+ZGenerational

非分代 ZGC通過命令行選項(xiàng)啟用 -XX:+UseZGC

ZGC的環(huán)境搭建

ZGC在設(shè)計(jì)上做到了自適應(yīng),根據(jù)運(yùn)行情況自動(dòng)調(diào)整參數(shù),讓用戶手動(dòng)配置的參數(shù)最少化。

  • 自動(dòng)設(shè)置年輕代大小,無需設(shè)置-Xmn參數(shù)。

自動(dòng)晉升閾值(復(fù)制中存活多少次才搬運(yùn)到老年代),無需設(shè)置-XX:TenuringThreshold。

JDK17之后支持自動(dòng)的并行線程數(shù),無需設(shè)置-XX:ConcGCThreads。

  • 需要設(shè)置的參數(shù):

    ??-Xmx 值 最大堆內(nèi)存大小

    ??這是ZGC最重要的一個(gè)參數(shù),必須設(shè)置。ZGC在運(yùn)行過程中會(huì)使用一部分內(nèi)存用來處理垃圾回收,所以盡量保證堆中有足夠的空間。設(shè)置多少值取決于對(duì)象分配的速度,根據(jù)測試情況來決定。

  • 可以設(shè)置的參數(shù):

    ??-XX:SoftMaxHeapSize=值

    ??ZGC會(huì)盡量保證堆內(nèi)存小于該值,這樣在內(nèi)存靠近這個(gè)值時(shí)會(huì)盡早地進(jìn)行垃圾回收,但是依然有可能會(huì)超過該值。

    ??例如,-Xmx5g -XX:SoftMaxHeapSize=4g 這個(gè)參數(shù)設(shè)置,ZGC會(huì)盡量保證堆內(nèi)存小于4GB,最多不會(huì)超過5GB。

@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseZGC","-XX:+UseLargePages"})
public void zGC(Blackhole blackhole){test(blackhole);
}@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseZGC","-XX:+ZGenerational","-XX:+UseLargePages"})
public void zGCGenerational(Blackhole blackhole){test(blackhole);
}

ZGC整體表現(xiàn)還是非常不錯(cuò)的,分代也讓ZGC的停頓時(shí)間有更好的表現(xiàn)。

ZGC調(diào)優(yōu)

ZGC 中可以使用Linux的Huge Page大頁技術(shù)優(yōu)化性能,提升吞吐量、降低延遲。

注意:安裝過程需要 root 權(quán)限,所以ZGC默認(rèn)沒有開啟此功能。

操作步驟:

1、計(jì)算所需頁數(shù),Linux x86架構(gòu)中大頁大小為2MB,根據(jù)所需堆內(nèi)存的大小估算大頁數(shù)量。比如堆空間需要16G,預(yù)留2G(JVM需要額外的一些非堆空間),那么頁數(shù)就是18G / 2MB = 9216。

2、配置系統(tǒng)的大頁池以具有所需的頁數(shù)(需要root權(quán)限):

$ echo 9216 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

3、添加參數(shù)-XX:+UseLargePages 啟動(dòng)程序進(jìn)行測試

2.4 實(shí)戰(zhàn)案例

需求:

Java服務(wù)中存在大量軟引用的緩存導(dǎo)致內(nèi)存不足,測試下g1、Shenandoah、ZGC這三種垃圾回收器在這種場景下的回收情況。

步驟:

測試代碼:

package com.itheima.jvmoptimize.fullgcdemo;import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.SneakyThrows;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;@RestController
@RequestMapping("/fullgc")
public class Demo2Controller {private Cache cache = Caffeine.newBuilder().weakKeys().softValues().build();private List<Object> objs = new ArrayList<>();private static final int _1MB = 1024 * 1024;//FULLGC測試//-Xms8g -Xmx8g -Xss256k -XX:MaxMetaspaceSize=512m  -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/test.hprof  -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps//ps + po 50并發(fā) 260ms  100并發(fā) 474  200并發(fā) 930//cms -XX:+UseParNewGC -XX:+UseConcMarkSweepGC 50并發(fā) 157ms  200并發(fā) 833//g1 JDK11 并發(fā)200 248@GetMapping("/1")public void test() throws InterruptedException {cache.put(RandomStringUtils.randomAlphabetic(8),new byte[10 * _1MB]);}}

1、啟動(dòng)程序,添加不同的虛擬機(jī)參數(shù)進(jìn)行測試。

2、使用Apache Benchmark測試工具對(duì)本機(jī)進(jìn)行壓測。

3、生成GC日志,使用GcEasy進(jìn)行分析。

4、對(duì)比壓測之后的結(jié)果。

兩種垃圾回收器在并行回收時(shí)都會(huì)使用垃圾回收線程占用CPU資源

在內(nèi)存足夠的情況下,ZGC垃圾回收表現(xiàn)的效果會(huì)更好,停頓時(shí)間更短。

在內(nèi)存不是特別充足的情況下, Shenandoah GC表現(xiàn)更好,并行垃圾回收的時(shí)間較短,用戶請(qǐng)求的執(zhí)行效率比較高。

3 揭秘Java工具

在Java的世界中,除了Java編寫的業(yè)務(wù)系統(tǒng)之外,還有一類程序也需要Java程序員參與編寫,這類程序就是Java工具。

常見的Java工具有以下幾類:

1、診斷類工具,如Arthas、VisualVM等。

2、開發(fā)類工具,如Idea、Eclipse。

3、APM應(yīng)用性能監(jiān)測工具,如Skywalking、Zipkin等。

4、熱部署工具,如Jrebel等。

3.1 Java工具的核心:Java Agent技術(shù)

Java Agent技術(shù)是JDK提供的用來編寫Java工具的技術(shù),使用這種技術(shù)生成一種特殊的jar包,這種jar包可以讓Java程序運(yùn)行其中的代碼。

Java Agent技術(shù)實(shí)現(xiàn)了讓Java程序執(zhí)行獨(dú)立的Java Agent程序中的代碼,執(zhí)行方式有兩種:

  • 靜態(tài)加載模式

靜態(tài)加載模式可以在程序啟動(dòng)的一開始就執(zhí)行我們需要執(zhí)行的代碼,適合用APM等性能監(jiān)測系統(tǒng)從一開始就監(jiān)控程序的執(zhí)行性能。靜態(tài)加載模式需要在Java Agent的項(xiàng)目中編寫一個(gè)premain的方法,并打包成jar包。

接下來使用以下命令啟動(dòng)Java程序,此時(shí)Java虛擬機(jī)將會(huì)加載agent中的代碼并執(zhí)行。

premain方法會(huì)在主線程中執(zhí)行:

  • 動(dòng)態(tài)加載模式

動(dòng)態(tài)加載模式可以隨時(shí)讓java agent代碼執(zhí)行,適用于Arthas等診斷系統(tǒng)。動(dòng)態(tài)加載模式需要在Java Agent的項(xiàng)目中編寫一個(gè)agentmain的方法,并打包成jar包。

接下來使用以下代碼就可以讓java agent代碼在指定的java進(jìn)程中執(zhí)行了。

agentmain方法會(huì)在獨(dú)立線程中執(zhí)行:

3.1.1 搭建java agent靜態(tài)加載模式的環(huán)境

步驟:

1、創(chuàng)建maven項(xiàng)目,添加maven-assembly-plugin插件,此插件可以打包出java agent的jar包。

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifestFile>src/main/resources/MANIFEST.MF</manifestFile></archive></configuration>
</plugin>

2、編寫類和premain方法,premain方法中打印一行信息。

public class AgentDemo {/*** 參數(shù)添加模式 啟動(dòng)java主程序時(shí)添加 -javaangent:agent路徑* @param agentArgs* @param inst*/public static void premain(String agentArgs, Instrumentation inst) {System.out.println("java agent執(zhí)行了...");}
}

3、編寫MANIFEST.MF文件,此文件主要用于描述java agent的配置屬性,比如使用哪一個(gè)類的premain方法。

Manifest-Version: 1.0
Premain-Class: com.itheima.jvm.javaagent.AgentDemo
Agent-Class: com.itheima.jvm.javaagent.AgentDemo
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true

4、使用maven-assembly-plugin進(jìn)行打包。

5、創(chuàng)建spring boot應(yīng)用,并靜態(tài)加載上一步打包完的java agent。

3.1.2 搭建java agent動(dòng)態(tài)加載模式的環(huán)境

步驟:

1、創(chuàng)建maven項(xiàng)目,添加maven-assembly-plugin插件,此插件可以打包出java agent的jar包。

2、編寫類和agentmain方法, agentmain方法中打印一行信息。

package com.itheima.jvm.javaagent.demo01;import java.lang.instrument.Instrumentation;public class AgentDemo {/*** 參數(shù)添加模式 啟動(dòng)java主程序時(shí)添加 -javaangent:agent路徑* @param agentArgs* @param inst*/public static void premain(String agentArgs, Instrumentation inst) {System.out.println("java agent執(zhí)行了...");}/*** attach 掛載模式 java主程序運(yùn)行之后,隨時(shí)可以將agent掛載上去*/public static void agentmain(String agentArgs, Instrumentation inst) {//打印線程名稱System.out.println(Thread.currentThread().getName());System.out.println("attach模式執(zhí)行了...");}
}

3、編寫MANIFEST.MF文件,此文件主要用于描述java agent的配置屬性,比如使用哪一個(gè)類的agentmain方法。

4、使用maven-assembly-plugin進(jìn)行打包。

5、編寫main方法,動(dòng)態(tài)連接到運(yùn)行中的java程序。

package com.itheima.jvm.javaagent.demo01;import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;import java.io.IOException;public class AttachMain {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {VirtualMachine vm = VirtualMachine.attach("24200");vm.loadAgent("D:\\jvm-java-agent\\target\\itheima-jvm-java-agent-jar-with-dependencies.jar");}
}

3.2 實(shí)戰(zhàn)案例1:簡化版的Arthas

需求:

編寫一個(gè)簡化版的Arthas程序,具備以下幾個(gè)功能:

1、查看內(nèi)存使用情況

2、生成堆內(nèi)存快照

3、打印棧信息

4、打印類加載器

5、打印類的源碼

6、打印方法執(zhí)行的參數(shù)和耗時(shí)

需求:

該程序是一個(gè)獨(dú)立的Jar包,可以應(yīng)用于任何Java編寫的系統(tǒng)中。

具備以下特點(diǎn):代碼無侵入性、操作簡單、性能高。

3.2.1 查看內(nèi)存使用情況

JDK從1.5開始提供了Java Management Extensions (JMX) 技術(shù),通過Mbean對(duì)象的寫入和獲取,實(shí)現(xiàn):

運(yùn)行時(shí)配置的獲取和更改

應(yīng)用程序運(yùn)行信息的獲取(線程棧、內(nèi)存、類信息等)

獲取JVM默認(rèn)提供的Mbean可以通過如下的方式,例如獲取內(nèi)存信息:

ManagementFactory提供了一系列的方法獲取各種各樣的信息:

package com.itheima.jvm.javaagent.demo02;import java.lang.instrument.Instrumentation;
import java.lang.management.*;
import java.util.List;/*** 1、查詢所有進(jìn)程* 2、顯示內(nèi)存相關(guān)的信息*/
public class AgentDemo {/*** 參數(shù)添加模式 啟動(dòng)java主程序時(shí)添加 -javaangent:agent路徑* @param agentArgs* @param inst*/public static void premain(String agentArgs, Instrumentation inst) {System.out.println("java agent執(zhí)行了...");}/*** attach 掛載模式 java主程序運(yùn)行之后,隨時(shí)可以將agent掛載上去*///-XX:+UseSerialGC -Xmx1g -Xms512mpublic static void agentmain(String agentArgs, Instrumentation inst) {//打印內(nèi)存的使用情況memory();}//獲取內(nèi)存信息private static void memory(){List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();System.out.println("堆內(nèi)存:");//獲取堆內(nèi)存getMemoryInfo(memoryPoolMXBeans, MemoryType.HEAP);//獲取非堆內(nèi)存System.out.println("非堆內(nèi)存:");getMemoryInfo(memoryPoolMXBeans, MemoryType.NON_HEAP);//nio使用的直接內(nèi)存try{@SuppressWarnings("rawtypes")Class bufferPoolMXBeanClass = Class.forName("java.lang.management.BufferPoolMXBean");@SuppressWarnings("unchecked")List<BufferPoolMXBean> bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(bufferPoolMXBeanClass);for (BufferPoolMXBean mbean : bufferPoolMXBeans) {StringBuilder sb = new StringBuilder();sb.append("name:").append(mbean.getName()).append(" used:").append(mbean.getMemoryUsed()/ 1024 / 1024).append("m").append(" max:").append(mbean.getTotalCapacity() / 1024 / 1024).append("m");System.out.println(sb);}}catch (Exception e){System.out.println(e);}}private static void getMemoryInfo(List<MemoryPoolMXBean> memoryPoolMXBeans, MemoryType heap) {memoryPoolMXBeans.stream().filter(x -> x.getType().equals(heap)).forEach(x -> {StringBuilder sb = new StringBuilder();sb.append("name:").append(x.getName()).append(" used:").append(x.getUsage().getUsed() / 1024 / 1024).append("m").append(" max:").append(x.getUsage().getMax() / 1024 / 1024).append("m").append(" committed:").append(x.getUsage().getCommitted() / 1024 / 1024).append("m");System.out.println(sb);});}public static void main(String[] args) {memory();}
}

3.2.2 生成堆內(nèi)存快照

更多的信息可以通過ManagementFactory.getPlatformMXBeans獲取,比如:

通過這種方式,獲取到了Java虛擬機(jī)中分配的直接內(nèi)存和內(nèi)存映射緩沖區(qū)的大小。

獲取到虛擬機(jī)診斷用的MXBean,通過這個(gè)Bean對(duì)象可以生成內(nèi)存快照。

public static void heapDump(){SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm");String filename = simpleDateFormat.format(new Date()) + ".hprof";System.out.println("生成內(nèi)存dump文件,文件名為:" + filename);HotSpotDiagnosticMXBean hotSpotDiagnosticMXBean =ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);try {hotSpotDiagnosticMXBean.dumpHeap(filename, true);} catch (IOException e) {e.printStackTrace();}
}

3.2.3 打印棧信息

package com.itheima.jvm.javaagent.demo03;import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;public class ThreadCommand {public static void printStackInfo(){ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();ThreadInfo[] infos = threadMXBean.dumpAllThreads(threadMXBean.isObjectMonitorUsageSupported(),threadMXBean.isSynchronizerUsageSupported());for (ThreadInfo info : infos) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("name:").append(info.getThreadName()).append(" threadId:").append(info.getThreadId()).append(" state:").append(info.getThreadState());System.out.println(stringBuilder);StackTraceElement[] stackTrace = info.getStackTrace();for (StackTraceElement stackTraceElement : stackTrace) {System.out.println(stackTraceElement.toString());}System.out.println();}}public static void main(String[] args) {printStackInfo();}
}

3.2.4 打印類加載器

Java Agent中可以獲得Java虛擬機(jī)提供的Instumentation對(duì)象:

該對(duì)象有以下幾個(gè)作用:

1、redefine,重新設(shè)置類的字節(jié)碼信息。

2、retransform,根據(jù)現(xiàn)有類的字節(jié)碼信息進(jìn)行增強(qiáng)。

3、獲取所有已加載的類信息。

Oracle官方手冊(cè): https://docs.oracle.com/javase/17/docs/api/java/lang/instrument/Instrumentation.html

package com.itheima.jvm.javaagent.demo04;import org.jd.core.v1.ClassFileToJavaSourceDecompiler;
import org.jd.core.v1.api.loader.Loader;
import org.jd.core.v1.api.loader.LoaderException;
import org.jd.core.v1.api.printer.Printer;import java.lang.instrument.*;
import java.security.ProtectionDomain;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.stream.Collectors;public class ClassCommand {//獲取所有類加載器private static Set<ClassLoader> getAllClassLoader(Instrumentation inst){HashSet<ClassLoader> classLoaders = new HashSet<>();Class[] allLoadedClasses = inst.getAllLoadedClasses();for (Class clazz : allLoadedClasses) {ClassLoader classLoader = clazz.getClassLoader();classLoaders.add(classLoader);}return classLoaders;}public static void printAllClassLoader(Instrumentation inst){Set<ClassLoader> allClassLoader = getAllClassLoader(inst);String result = allClassLoader.stream().map(x -> {if (x ==null) {return "BootStrapClassLoader";} else {return x.toString();}}).distinct().sorted(String::compareTo).collect(Collectors.joining(","));System.out.println(result);}}

3.2.5 打印類的源碼

打印類的源碼需要分為以下幾個(gè)步驟

1、獲得內(nèi)存中的類的字節(jié)碼信息。利用Instrumentation提供的轉(zhuǎn)換器來獲取字節(jié)碼信息。

2、通過反編譯工具將字節(jié)碼信息還原成源代碼信息。

這里我們會(huì)使用jd-core依賴庫來完成,github地址:https://github.com/java-decompiler/jd-core

Pom添加依賴:

<dependency><groupId>org.jd</groupId><artifactId>jd-core</artifactId><version>1.1.3</version>
</dependency>

//獲取類信息
public static void printClass(Instrumentation inst){Scanner scanner = new Scanner(System.in);System.out.println("請(qǐng)輸入類名:");String next = scanner.next();Class[] allLoadedClasses = inst.getAllLoadedClasses();System.out.println("要查找的類名是:" + next);//匹配類名for (Class clazz : allLoadedClasses) {if(clazz.getName().equals(next)){System.out.println("找到了類,類加載器為:" + clazz.getClassLoader());ClassFileTransformer transformer = new ClassFileTransformer() {@Overridepublic byte[] transform(Module module, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {ClassFileToJavaSourceDecompiler classFileToJavaSourceDecompiler = new ClassFileToJavaSourceDecompiler();Printer printer = new Printer() {protected static final String TAB = "  ";protected static final String NEWLINE = "\n";protected int indentationCount = 0;protected StringBuilder sb = new StringBuilder();@Override public String toString() { return sb.toString(); }@Override public void start(int maxLineNumber, int majorVersion, int minorVersion) {}@Override public void end() {System.out.println(sb.toString());}@Override public void printText(String text) { sb.append(text); }@Override public void printNumericConstant(String constant) { sb.append(constant); }@Override public void printStringConstant(String constant, String ownerInternalName) { sb.append(constant); }@Override public void printKeyword(String keyword) { sb.append(keyword); }@Override public void printDeclaration(int type, String internalTypeName, String name, String descriptor) { sb.append(name); }@Override public void printReference(int type, String internalTypeName, String name, String descriptor, String ownerInternalName) { sb.append(name); }@Override public void indent() { this.indentationCount++; }@Override public void unindent() { this.indentationCount--; }@Override public void startLine(int lineNumber) { for (int i=0; i<indentationCount; i++) sb.append(TAB); }@Override public void endLine() { sb.append(NEWLINE); }@Override public void extraLine(int count) { while (count-- > 0) sb.append(NEWLINE); }@Override public void startMarker(int type) {}@Override public void endMarker(int type) {}};try {classFileToJavaSourceDecompiler.decompile(new Loader() {@Overridepublic boolean canLoad(String s) {return false;}@Overridepublic byte[] load(String s) throws LoaderException {return classfileBuffer;}},printer,className);} catch (Exception e) {e.printStackTrace();}//System.out.println(new String(classfileBuffer));return ClassFileTransformer.super.transform(module, loader, className, classBeingRedefined, protectionDomain, classfileBuffer);}};inst.addTransformer(transformer,true);try {inst.retransformClasses(clazz);} catch (UnmodifiableClassException e) {e.printStackTrace();}finally {inst.removeTransformer(transformer);}}}
}

3.2.6 打印方法執(zhí)行的參數(shù)和耗時(shí)

Spring AOP是不是也可以實(shí)現(xiàn)類似的功能呢?

Spring AOP 確實(shí)可以實(shí)現(xiàn)統(tǒng)計(jì)方法執(zhí)行時(shí)間,打印方法參數(shù)等功能,但是使用這種方式存在幾個(gè)問題:

① 代碼有侵入性,AOP代碼必須在當(dāng)前項(xiàng)目中被引入才能完成相應(yīng)的功能。

② 無法做到靈活地開啟和關(guān)閉功能。

③ 與Spring框架強(qiáng)耦合,如果項(xiàng)目沒有使用Spring框架就不可以使用。

所以使用Java Agent技術(shù) + 字節(jié)碼增強(qiáng)技術(shù),就可以解決上述三個(gè)問題。

3.2.6.1 ASM字節(jié)碼增強(qiáng)技術(shù)

打印方法執(zhí)行的參數(shù)和耗時(shí)需要對(duì)原始類的方法進(jìn)行增強(qiáng),可以使用類似于Spring AOP這類面向切面編程的方式,但是考慮到并非每個(gè)項(xiàng)目都使用了Spring這些框架,所以我們選擇的是最基礎(chǔ)的字節(jié)碼增強(qiáng)框架。字節(jié)碼增強(qiáng)框架是在當(dāng)前類的字節(jié)碼信息中插入一部分字節(jié)碼指令,從而起到增強(qiáng)的作用。

ASM是一個(gè)通用的 Java 字節(jié)碼操作和分析框架。它可用于直接以二進(jìn)制形式修改現(xiàn)有類或動(dòng)態(tài)生成類。ASM重點(diǎn)關(guān)注性能。讓操作盡可能小且盡可能快,所以它非常適合在動(dòng)態(tài)系統(tǒng)中使用。ASM的缺點(diǎn)是代碼復(fù)雜。

ASM的官方網(wǎng)址:https://asm.ow2.io/

操作步驟:

1、引入依賴

<dependency><groupId>org.ow2.asm</groupId><artifactId>asm</artifactId><version>9.6</version>
</dependency>

2、搭建基礎(chǔ)框架,此代碼為固定代碼。

3、編寫一個(gè)類描述如何去增強(qiáng)類,類需要繼承自MethodVisitor

ASM基礎(chǔ)案例:

package com.itheima.jvm.javaagent.demo05;import org.objectweb.asm.*;import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;import static org.objectweb.asm.Opcodes.*;public class ASMDemo {public static byte[] classASM(byte[] bytes){ClassWriter cw = new ClassWriter(0);// cv forwards all events to cwClassVisitor cv = new ClassVisitor(ASM7, cw) {@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);return new MyMethodVisitor(this.api,mv);}};ClassReader cr = new ClassReader(bytes);cr.accept(cv, 0);return cw.toByteArray();}public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {InputStream inputStream = ASMDemo.class.getResourceAsStream("/com/itheima/jvm/javaagent/demo05/ASMDemo.class");byte[] b1 = inputStream.readAllBytes();byte[] b2 = classASM(b1); // b2 represents the same class as b1//創(chuàng)建類加載器MyClassLoader myClassLoader = new MyClassLoader();Class clazz = myClassLoader.defineClass("com.itheima.jvm.javaagent.demo05.ASMDemo", b2);clazz.getDeclaredConstructor().newInstance();}
}class MyClassLoader extends ClassLoader {public Class defineClass(String name, byte[] b) {return defineClass(name, b, 0, b.length);}
}class MyMethodVisitor extends MethodVisitor {public MyMethodVisitor(int api, MethodVisitor methodVisitor) {super(api, methodVisitor);}@Overridepublic void visitCode() {mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");mv.visitLdcInsn("開始執(zhí)行");mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);super.visitCode();}@Overridepublic void visitInsn(int opcode) {if(opcode == ARETURN || opcode == RETURN ) {mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");mv.visitLdcInsn("結(jié)束執(zhí)行");mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);}super.visitInsn(opcode);}@Overridepublic void visitEnd() {mv.visitMaxs(20,50);super.visitEnd();}}
3.2.6.2 Byte Buddy字節(jié)碼增強(qiáng)技術(shù)

Byte Buddy 是一個(gè)代碼生成和操作庫,用于在 Java 應(yīng)用程序運(yùn)行時(shí)創(chuàng)建和修改 Java 類,而無需編譯器的幫助。 Byte Buddy底層基于ASM,提供了非常方便的 API。

Byte Buddy官網(wǎng): https://bytebuddy.net/

操作步驟:

1、引入依賴

<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.14.10</version>
</dependency>
<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId><version>1.14.10</version>
</dependency>

2、搭建基礎(chǔ)框架,此代碼為固定代碼

3、編寫一個(gè)Advice通知描述如何去增強(qiáng)類

package com.itheima.jvm.javaagent.demo05;import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;public class ByteBuddyDemo {public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {Foo foo = new Foo();MyClassLoader myClassLoader = new MyClassLoader();Class<? extends Foo> newClazz = new ByteBuddy().subclass(Foo.class).method(ElementMatchers.any()).intercept(Advice.to(MyAdvice.class)).make().load(myClassLoader).getLoaded();Foo foo1 = newClazz.getDeclaredConstructor().newInstance();foo1.test();}
}class MyAdvice {@Advice.OnMethodEnterstatic void onEnter(){System.out.println("方法進(jìn)入");}@Advice.OnMethodExitstatic void onExit(){System.out.println("方法退出");}}

增強(qiáng)后的代碼:

package com.itheima.jvm.javaagent.demo05;import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.jd.core.v1.ClassFileToJavaSourceDecompiler;
import org.jd.core.v1.api.loader.Loader;
import org.jd.core.v1.api.loader.LoaderException;
import org.jd.core.v1.api.printer.Printer;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.Scanner;import static net.bytebuddy.matcher.ElementMatchers.isMethod;public class ClassEnhancerCommand {//獲取類信息public static void enhanceClass(Instrumentation inst){Scanner scanner = new Scanner(System.in);System.out.println("請(qǐng)輸入類名:");String next = scanner.next();Class[] allLoadedClasses = inst.getAllLoadedClasses();System.out.println("要查找的類名是:" + next);//匹配類名for (Class clazz : allLoadedClasses) {if(clazz.getName().equals(next)){System.out.println("找到了類,類加載器為:" + clazz.getClassLoader());new AgentBuilder.Default().disableClassFormatChanges().with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION).with( //new AgentBuilder.Listener.WithErrorsOnly(new AgentBuilder.Listener.WithTransformationsOnly(AgentBuilder.Listener.StreamWriting.toSystemOut()))//.type(ElementMatchers.isAnnotatedWith(named("org.springframework.web.bind.annotation.RestController"))).type(ElementMatchers.named(clazz.getName())).transform((builder, type, classLoader, module, protectionDomain) ->builder.visit(Advice.to(MyAdvice.class).on(ElementMatchers.any()))
//                                builder .method(ElementMatchers.any())
//                                        .intercept(MethodDelegation.to(MyInterceptor.class))).installOn(inst);}}}
}

package com.itheima.jvm.javaagent.demo07;import net.bytebuddy.asm.Advice;class MyAdvice {@Advice.OnMethodEnterstatic long enter(@Advice.AllArguments Object[] ary) {if(ary != null) {for(int i =0 ; i < ary.length ; i++){System.out.println("Argument: " + i + " is " + ary[i]);}}return System.nanoTime();}@Advice.OnMethodExitstatic void exit(@Advice.Enter long value) {System.out.println("耗時(shí)為:" + (System.nanoTime() - value) + "納秒");}
}

最后將整個(gè)簡化版的arthas進(jìn)行打包,在服務(wù)器上進(jìn)行測試。使用maven-shade-plugin插件可以將所有依賴打入同一個(gè)jar包中并指定入口main方法。

<!--打包成jar包使用--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>1.4</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><finalName>itheima-attach-agent</finalName><transformers><!--java -jar 默認(rèn)啟動(dòng)的主類--><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>com.itheima.jvm.javaagent.AttachMain</mainClass></transformer></transformers></configuration></execution></executions>
</plugin>

3.3 實(shí)戰(zhàn)案例2:APM系統(tǒng)的數(shù)據(jù)采集

Application performance monitor (APM) 應(yīng)用程序性能監(jiān)控系統(tǒng)是采集運(yùn)行程序的實(shí)時(shí)數(shù)據(jù)并使用可視化的方式展示,使用APM可以確保系統(tǒng)可用性,優(yōu)化服務(wù)性能和響應(yīng)時(shí)間,持續(xù)改善用戶體驗(yàn)。常用的APM系統(tǒng)有Apache Skywalking、Zipkin等。

Skywalking官方網(wǎng)站: https://skywalking.apache.org/

需求:

編寫一個(gè)簡化版的APM數(shù)據(jù)采集程序,具備以下幾個(gè)功能:

1、無侵入性獲取spring boot應(yīng)用中,controller層方法的調(diào)用時(shí)間。

2、將所有調(diào)用時(shí)間寫入文件中。

問題:

Java agent 采用靜態(tài)加載模式 還是 動(dòng)態(tài)加載模式?

一般程序啟動(dòng)之后就需要持續(xù)地進(jìn)行信息的采集,所以采用靜態(tài)加載模式。

3.3.1 Java Agent參數(shù)的獲取

在Java Agent中,可以通過如下的方式傳遞參數(shù):

java -javaagent:./agent.jar=參數(shù) -jar test.jar

接下來通過premain參數(shù)中的agentArgs字段獲取:

如果有多個(gè)參數(shù),可以使用如下方式:

java -javaagent:./agent.jar=param1=value1,param2=value2 -jar test.jar

在Java代碼中使用字符串解析出對(duì)應(yīng)的key value。

在Java Agent中如果需要傳遞參數(shù)到Byte Buddy,可以采用如下的方式:

1、綁定Key Value,Key是一個(gè)自定義注解,Value是參數(shù)的值。

2、自定義注解

3、通過注解注入

代碼:

package com.itheima.javaagent;import com.itheima.javaagent.command.ClassCommand;
import com.itheima.javaagent.command.MemoryCommand;
import com.itheima.javaagent.command.ThreadCommand;
import com.itheima.javaagent.enhancer.AgentParam;
import com.itheima.javaagent.enhancer.MyAdvice;
import com.itheima.javaagent.enhancer.TimingAdvice;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;import java.lang.instrument.Instrumentation;
import java.util.Scanner;public class AgentMain {//premain方法public static void premain(String agentArgs, Instrumentation inst){//使用bytebuddy增強(qiáng)類new AgentBuilder.Default()//禁止byte buddy處理時(shí)修改類名.disableClassFormatChanges()//處理時(shí)使用retransform增強(qiáng).with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)//打印出錯(cuò)誤日志.with(new AgentBuilder.Listener.WithTransformationsOnly(AgentBuilder.Listener.StreamWriting.toSystemOut()))//匹配哪些類.type(ElementMatchers.isAnnotatedWith(ElementMatchers.named("org.springframework.web.bind.annotation.RestController").or(ElementMatchers.named("org.springframework.web.bind.annotation.Controller"))))//增強(qiáng),使用MyAdvice通知,對(duì)所有方法都進(jìn)行增強(qiáng).transform((builder, typeDescription, classLoader, module, protectionDomain) ->builder.visit(Advice.withCustomMapping().bind(AgentParam.class,agentArgs).to(TimingAdvice.class).on(ElementMatchers.any()))).installOn(inst);}}

package com.itheima.javaagent.enhancer;import net.bytebuddy.asm.Advice;
import org.apache.commons.io.FileUtils;import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;//統(tǒng)計(jì)耗時(shí),打印方法名、類名
public class TimingAdvice {//方法進(jìn)入時(shí),返回開始時(shí)間@Advice.OnMethodEnterstatic long enter(){return System.nanoTime();}//方法退出時(shí)候,統(tǒng)計(jì)方法執(zhí)行耗時(shí)@Advice.OnMethodExitstatic void exit(@Advice.Enter long value,@Advice.Origin("#t") String className,@Advice.Origin("#m") String methodName,@AgentParam("agent.log") String fileName){String str = methodName + "@" + className + "耗時(shí)為: " + (System.nanoTime() - value) + "納秒\n";try {FileUtils.writeStringToFile(new File(fileName),str, StandardCharsets.UTF_8,true);} catch (IOException e) {e.printStackTrace();}}
}

修改jar包名字,并重新打包:

啟動(dòng)spring boot服務(wù)時(shí),添加javaagent的路徑,并添加文件名參數(shù):

打印結(jié)果:

3.3.2 總結(jié)

Arthas這款工具用到了什么Java技術(shù),有沒有了解過?

回答:

Arthas主要使用了Java Agent技術(shù),這種技術(shù)可以讓運(yùn)行中的Java程序執(zhí)行Agent中編寫代碼。

Arthas使用了Agent中的動(dòng)態(tài)加載模式,可以選擇讓某個(gè)特定的Java進(jìn)程加載Agent并執(zhí)行其中的監(jiān)控代碼。監(jiān)控方面主要使用的就是JMX提供的一些監(jiān)控指標(biāo),同時(shí)使用字節(jié)碼增強(qiáng)技術(shù),對(duì)某些類和某些方法進(jìn)行增強(qiáng),從而監(jiān)控方法的執(zhí)行耗時(shí)、參數(shù)等內(nèi)容。

APM系統(tǒng)是如何獲取到Java程序運(yùn)行中的性能數(shù)據(jù)的?

回答:

APM系統(tǒng)比如Skywalking主要使用了Java Agent技術(shù),這種技術(shù)可以讓運(yùn)行中的Java程序執(zhí)行Agent中編寫代碼。

Skywalking編寫了Java Agent,使用了Agent中的靜態(tài)加載模式,使用字節(jié)碼增強(qiáng)技術(shù),對(duì)某些類和某些方法進(jìn)行增強(qiáng),從而監(jiān)控方法的執(zhí)行耗時(shí)、參數(shù)等內(nèi)容。比如對(duì)Controller層方法增強(qiáng),獲取接口調(diào)用的時(shí)長信息,對(duì)數(shù)據(jù)庫連接增強(qiáng),獲取數(shù)據(jù)庫查詢的時(shí)長、SQL語句等信息。

http://aloenet.com.cn/news/43802.html

相關(guān)文章:

  • wordpress怎么仿站資訊門戶類網(wǎng)站有哪些
  • 旺旺號(hào)查詢網(wǎng)站怎么做好的seo網(wǎng)站
  • 網(wǎng)站圖片鏈接是怎么做的百度關(guān)鍵詞排名技術(shù)
  • 上海企樂網(wǎng)站制作公司河南專業(yè)網(wǎng)站建設(shè)
  • 佛山網(wǎng)站建設(shè)怎樣做搜索引擎營銷的主要方法
  • 重慶網(wǎng)站建設(shè)挑夾夾蟲徐州seo網(wǎng)站推廣
  • 新橋?qū)I(yè)網(wǎng)站建設(shè)智能建站平臺(tái)
  • 做美食網(wǎng)站的需求寧波seo快速優(yōu)化
  • 冠縣網(wǎng)站建設(shè)價(jià)格百度推廣客戶端電腦版
  • 免費(fèi)搭網(wǎng)站常州seo第一人
  • 中職教師資格證網(wǎng)站建設(shè)與管理鄭州seo外包費(fèi)用
  • 超級(jí)工程網(wǎng)站建設(shè)網(wǎng)站優(yōu)化排名的方法
  • 織夢后臺(tái)怎么加自己做的網(wǎng)站長春seo快速排名
  • 開發(fā)公司起名seo網(wǎng)站快速排名
  • 微網(wǎng)站和小程序的區(qū)別站長統(tǒng)計(jì)app下載大全
  • 彩票娛樂網(wǎng)站建設(shè)開發(fā)百度競價(jià)排名正確解釋
  • 做信息網(wǎng)站怎么賺錢網(wǎng)絡(luò)營銷廣告策劃
  • 杭州灣新區(qū)建設(shè)局網(wǎng)站營銷咨詢師
  • 解決做網(wǎng)站問題上海最新新聞
  • 網(wǎng)站bbs備案龍崗網(wǎng)站設(shè)計(jì)
  • 做網(wǎng)站備案湯陰縣seo快速排名有哪家好
  • 財(cái)務(wù)咨詢網(wǎng)站模板長沙縣網(wǎng)絡(luò)營銷咨詢
  • 個(gè)人網(wǎng)站做淘寶客犯法嗎寫軟文怎么接單子
  • 網(wǎng)站開發(fā)的主要特點(diǎn)網(wǎng)絡(luò)推廣公司網(wǎng)站
  • 汕頭住房與城鄉(xiāng)建設(shè)網(wǎng)站實(shí)體店引流推廣方法
  • 阿米納網(wǎng)站建設(shè)網(wǎng)上互聯(lián)網(wǎng)推廣
  • 做淘寶聯(lián)盟網(wǎng)站要多少錢百度高級(jí)搜索技巧
  • 阿里巴巴網(wǎng)站分類板塊做全屏全網(wǎng)營銷推廣方案
  • 成都房地產(chǎn)最新政策seo是哪個(gè)英文的縮寫
  • 做外貿(mào)網(wǎng)站選美國服務(wù)器的費(fèi)用百度愛采購優(yōu)化