【Kafka】Kafka-Server-start.sh 啟動腳本分析(Ver 2.7.2)
Kafka-Server-start.sh
??
if?[?$#?-lt?1?];??
then??
?#?提示命令使用方法
?echo?"USAGE:?$0?[-daemon]?server.properties?[--override?property=value]*"?exit?1??
fi??
base_dir=$(dirname?$0)??
??
if?[?"x$KAFKA_LOG4J_OPTS"?=?"x"?];?then??
????export?KAFKA_LOG4J_OPTS="-Dlog4j.configuration=file:$base_dir/../config/log4j.properties"??
fi??
??
if?[?"x$KAFKA_HEAP_OPTS"?=?"x"?];?then??
????export?KAFKA_HEAP_OPTS="-Xmx1G?-Xms1G"??
fi??
??
EXTRA_ARGS=${EXTRA_ARGS-'-name?kafkaServer?-loggc'}??
??
COMMAND=$1??
case?$COMMAND?in??
??-daemon)??
????EXTRA_ARGS="-daemon?"$EXTRA_ARGS??
????shift??
????;;??
??*)??
????;;??
esac??
??
exec?$base_dir/kafka-run-class.sh?$EXTRA_ARGS?kafka.Kafka?"$@"
判斷參數(shù)有沒有,參數(shù)個數(shù)小于1就提示用法;
獲取腳本當(dāng)前路徑賦值給變量 base_dir;
判斷日志參數(shù) KAFKA_LOG4J_OPTS 是否為空,為空就給它一個值;
判斷堆參數(shù) KAFKA_HEAP_OPTS是否為空,為空就默認(rèn)給它賦值為 "-Xmx1G -Xms1G",默認(rèn)的堆空間指定為1G;
判斷啟動命令中第一個參數(shù)是否為
-daemon
,如果是就以守護(hù)進(jìn)程啟動(其實不是,是賦給另一個變量 EXTRA_ARGS);執(zhí)行命令。
最后一個腳本是執(zhí)行另一個腳本:kafka-run-class.sh
,這個腳本的內(nèi)容比較復(fù)雜了。
kafka-run-class.sh
if?[?$#?-lt?1?];
then
??echo?"USAGE:?$0?[-daemon]?[-name?servicename]?[-loggc]?classname?[opts]"
??exit?1
fi
#?CYGWIN?==?1?if?Cygwin?is?detected,?else?0.
if?[[?$(uname?-a)?=~?"CYGWIN"?]];?then
??CYGWIN=1
else
??CYGWIN=0
fi
if?[?-z?"$INCLUDE_TEST_JARS"?];?then
??INCLUDE_TEST_JARS=false
fi
#?Exclude?jars?not?necessary?for?running?commands.
regex="(-(test|test-sources|src|scaladoc|javadoc)\.jar|jar.asc)$"
should_include_file()?{
??if?[?"$INCLUDE_TEST_JARS"?=?true?];?then
????return?0
??fi
??file=$1
??if?[?-z?"$(echo?"$file"?|?egrep?"$regex")"?]?;?then
????return?0
??else
????return?1
??fi
}
base_dir=$(dirname?$0)/..
if?[?-z?"$SCALA_VERSION"?];?then
??SCALA_VERSION=2.13.3
??if?[[?-f?"$base_dir/gradle.properties"?]];?then
????SCALA_VERSION=`grep?"^scalaVersion="?"$base_dir/gradle.properties"?|?cut?-d=?-f?2`
??fi
fi
if?[?-z?"$SCALA_BINARY_VERSION"?];?then
??SCALA_BINARY_VERSION=$(echo?$SCALA_VERSION?|?cut?-f?1-2?-d?'.')
fi
#?run?./gradlew?copyDependantLibs?to?get?all?dependant?jars?in?a?local?dir
shopt?-s?nullglob
if?[?-z?"$UPGRADE_KAFKA_STREAMS_TEST_VERSION"?];?then
??for?dir?in?"$base_dir"/core/build/dependant-libs-${SCALA_VERSION}*;
??do
????CLASSPATH="$CLASSPATH:$dir/*"
??done
fi
for?file?in?"$base_dir"/examples/build/libs/kafka-examples*.jar;
do
??if?should_include_file?"$file";?then
????CLASSPATH="$CLASSPATH":"$file"
??fi
done
if?[?-z?"$UPGRADE_KAFKA_STREAMS_TEST_VERSION"?];?then
??clients_lib_dir=$(dirname?$0)/../clients/build/libs
??streams_lib_dir=$(dirname?$0)/../streams/build/libs
??streams_dependant_clients_lib_dir=$(dirname?$0)/../streams/build/dependant-libs-${SCALA_VERSION}
else
??clients_lib_dir=/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs
??streams_lib_dir=$clients_lib_dir
??streams_dependant_clients_lib_dir=$streams_lib_dir
fi
for?file?in?"$clients_lib_dir"/kafka-clients*.jar;
do
??if?should_include_file?"$file";?then
????CLASSPATH="$CLASSPATH":"$file"
??fi
done
for?file?in?"$streams_lib_dir"/kafka-streams*.jar;
do
??if?should_include_file?"$file";?then
????CLASSPATH="$CLASSPATH":"$file"
??fi
done
if?[?-z?"$UPGRADE_KAFKA_STREAMS_TEST_VERSION"?];?then
??for?file?in?"$base_dir"/streams/examples/build/libs/kafka-streams-examples*.jar;
??do
????if?should_include_file?"$file";?then
??????CLASSPATH="$CLASSPATH":"$file"
????fi
??done
else
??VERSION_NO_DOTS=`echo?$UPGRADE_KAFKA_STREAMS_TEST_VERSION?|?sed?'s/\.//g'`
??SHORT_VERSION_NO_DOTS=${VERSION_NO_DOTS:0:((${#VERSION_NO_DOTS}?-?1))}?#?remove?last?char,?ie,?bug-fix?number
??for?file?in?"$base_dir"/streams/upgrade-system-tests-$SHORT_VERSION_NO_DOTS/build/libs/kafka-streams-upgrade-system-tests*.jar;
??do
????if?should_include_file?"$file";?then
??????CLASSPATH="$file":"$CLASSPATH"
????fi
??done
??if?[?"$SHORT_VERSION_NO_DOTS"?=?"0100"?];?then
????CLASSPATH="/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs/zkclient-0.8.jar":"$CLASSPATH"
????CLASSPATH="/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs/zookeeper-3.4.6.jar":"$CLASSPATH"
??fi
??if?[?"$SHORT_VERSION_NO_DOTS"?=?"0101"?];?then
????CLASSPATH="/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs/zkclient-0.9.jar":"$CLASSPATH"
????CLASSPATH="/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs/zookeeper-3.4.8.jar":"$CLASSPATH"
??fi
fi
for?file?in?"$streams_dependant_clients_lib_dir"/rocksdb*.jar;
do
??CLASSPATH="$CLASSPATH":"$file"
done
for?file?in?"$streams_dependant_clients_lib_dir"/*hamcrest*.jar;
do
??CLASSPATH="$CLASSPATH":"$file"
done
for?file?in?"$base_dir"/tools/build/libs/kafka-tools*.jar;
do
??if?should_include_file?"$file";?then
????CLASSPATH="$CLASSPATH":"$file"
??fi
done
for?dir?in?"$base_dir"/tools/build/dependant-libs-${SCALA_VERSION}*;
do
??CLASSPATH="$CLASSPATH:$dir/*"
done
for?cc_pkg?in?"api"?"transforms"?"runtime"?"file"?"mirror"?"mirror-client"?"json"?"tools"?"basic-auth-extension"
do
??for?file?in?"$base_dir"/connect/${cc_pkg}/build/libs/connect-${cc_pkg}*.jar;
??do
????if?should_include_file?"$file";?then
??????CLASSPATH="$CLASSPATH":"$file"
????fi
??done
??if?[?-d?"$base_dir/connect/${cc_pkg}/build/dependant-libs"?]?;?then
????CLASSPATH="$CLASSPATH:$base_dir/connect/${cc_pkg}/build/dependant-libs/*"
??fi
done
#?classpath?addition?for?release
for?file?in?"$base_dir"/libs/*;
do
??if?should_include_file?"$file";?then
????CLASSPATH="$CLASSPATH":"$file"
??fi
done
for?file?in?"$base_dir"/core/build/libs/kafka_${SCALA_BINARY_VERSION}*.jar;
do
??if?should_include_file?"$file";?then
????CLASSPATH="$CLASSPATH":"$file"
??fi
done
shopt?-u?nullglob
if?[?-z?"$CLASSPATH"?]?;?then
??echo?"Classpath?is?empty.?Please?build?the?project?first?e.g.?by?running?'./gradlew?jar?-PscalaVersion=$SCALA_VERSION'"
??exit?1
fi
#?JMX?settings
if?[?-z?"$KAFKA_JMX_OPTS"?];?then
??KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote?-Dcom.sun.management.jmxremote.authenticate=false??-Dcom.sun.management.jmxremote.ssl=false?"
fi
#?JMX?port?to?use
if?[??$JMX_PORT?];?then
??KAFKA_JMX_OPTS="$KAFKA_JMX_OPTS?-Dcom.sun.management.jmxremote.port=$JMX_PORT?"
fi
#?Log?directory?to?use
if?[?"x$LOG_DIR"?=?"x"?];?then
??LOG_DIR="$base_dir/logs"
fi
#?Log4j?settings
if?[?-z?"$KAFKA_LOG4J_OPTS"?];?then
??#?Log?to?console.?This?is?a?tool.
??LOG4J_DIR="$base_dir/config/tools-log4j.properties"
??#?If?Cygwin?is?detected,?LOG4J_DIR?is?converted?to?Windows?format.
??((?CYGWIN?))?&&?LOG4J_DIR=$(cygpath?--path?--mixed?"${LOG4J_DIR}")
??KAFKA_LOG4J_OPTS="-Dlog4j.configuration=file:${LOG4J_DIR}"
else
??#?create?logs?directory
??if?[?!?-d?"$LOG_DIR"?];?then
????mkdir?-p?"$LOG_DIR"
??fi
fi
#?If?Cygwin?is?detected,?LOG_DIR?is?converted?to?Windows?format.
((?CYGWIN?))?&&?LOG_DIR=$(cygpath?--path?--mixed?"${LOG_DIR}")
KAFKA_LOG4J_OPTS="-Dkafka.logs.dir=$LOG_DIR?$KAFKA_LOG4J_OPTS"
#?Generic?jvm?settings?you?want?to?add
if?[?-z?"$KAFKA_OPTS"?];?then
??KAFKA_OPTS=""
fi
#?Set?Debug?options?if?enabled
if?[?"x$KAFKA_DEBUG"?!=?"x"?];?then
????#?Use?default?ports
????DEFAULT_JAVA_DEBUG_PORT="5005"
????if?[?-z?"$JAVA_DEBUG_PORT"?];?then
????????JAVA_DEBUG_PORT="$DEFAULT_JAVA_DEBUG_PORT"
????fi
????#?Use?the?defaults?if?JAVA_DEBUG_OPTS?was?not?set
????DEFAULT_JAVA_DEBUG_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND_FLAG:-n},address=$JAVA_DEBUG_PORT"
????if?[?-z?"$JAVA_DEBUG_OPTS"?];?then
????????JAVA_DEBUG_OPTS="$DEFAULT_JAVA_DEBUG_OPTS"
????fi
????echo?"Enabling?Java?debug?options:?$JAVA_DEBUG_OPTS"
????KAFKA_OPTS="$JAVA_DEBUG_OPTS?$KAFKA_OPTS"
fi
#?Which?java?to?use
if?[?-z?"$JAVA_HOME"?];?then
??JAVA="java"
else
??JAVA="$JAVA_HOME/bin/java"
fi
#?Memory?options
if?[?-z?"$KAFKA_HEAP_OPTS"?];?then
??KAFKA_HEAP_OPTS="-Xmx256M"
fi
#?JVM?performance?options
#?MaxInlineLevel=15?is?the?default?since?JDK?14?and?can?be?removed?once?older?JDKs?are?no?longer?supported
if?[?-z?"$KAFKA_JVM_PERFORMANCE_OPTS"?];?then
??KAFKA_JVM_PERFORMANCE_OPTS="-server?-XX:+UseG1GC?-XX:MaxGCPauseMillis=20?-XX:InitiatingHeapOccupancyPercent=35?-XX:+ExplicitGCInvokesConcurrent?-XX:MaxInlineLevel=15?-Djava.awt.headless=true"
fi
while?[?$#?-gt?0?];?do
??COMMAND=$1
??case?$COMMAND?in
????-name)
??????DAEMON_NAME=$2
??????CONSOLE_OUTPUT_FILE=$LOG_DIR/$DAEMON_NAME.out
??????shift?2
??????;;
????-loggc)
??????if?[?-z?"$KAFKA_GC_LOG_OPTS"?];?then
????????GC_LOG_ENABLED="true"
??????fi
??????shift
??????;;
????-daemon)
??????DAEMON_MODE="true"
??????shift
??????;;
????*)
??????break
??????;;
??esac
done
#?GC?options?
GC_FILE_SUFFIX='-gc.log'
GC_LOG_FILE_NAME=''
if?[?"x$GC_LOG_ENABLED"?=?"xtrue"?];?then
??GC_LOG_FILE_NAME=$DAEMON_NAME$GC_FILE_SUFFIX
??#?The?first?segment?of?the?version?number,?which?is?'1'?for?releases?before?Java?9
??#?it?then?becomes?'9',?'10',?...
??#?Some?examples?of?the?first?line?of?`java?--version`:
??#?8?->?java?version?"1.8.0_152"
??#?9.0.4?->?java?version?"9.0.4"
??#?10?->?java?version?"10"?2018-03-20
??#?10.0.1?->?java?version?"10.0.1"?2018-04-17
??#?We?need?to?match?to?the?end?of?the?line?to?prevent?sed?from?printing?the?characters?that?do?not?match
??JAVA_MAJOR_VERSION=$("$JAVA"?-version?2>&1?|?sed?-E?-n?'s/.*?version?"([0-9]*).*$/\1/p')
??if?[[?"$JAVA_MAJOR_VERSION"?-ge?"9"?]]?;?then
????KAFKA_GC_LOG_OPTS="-Xlog:gc*:file=$LOG_DIR/$GC_LOG_FILE_NAME:time,tags:filecount=10,filesize=100M"
??else
????KAFKA_GC_LOG_OPTS="-Xloggc:$LOG_DIR/$GC_LOG_FILE_NAME?-verbose:gc?-XX:+PrintGCDetails?-XX:+PrintGCDateStamps?-XX:+PrintGCTimeStamps?-XX:+UseGCLogFileRotation?-XX:NumberOfGCLogFiles=10?-XX:GCLogFileSize=100M"
??fi
fi
#?Remove?a?possible?colon?prefix?from?the?classpath?(happens?at?lines?like?`CLASSPATH="$CLASSPATH:$file"`?when?CLASSPATH?is?blank)
#?Syntax?used?on?the?right?side?is?native?Bash?string?manipulation;?for?more?details?see
#?http://tldp.org/LDP/abs/html/string-manipulation.html,?specifically?the?section?titled?"Substring?Removal"
CLASSPATH=${CLASSPATH#:}
#?If?Cygwin?is?detected,?classpath?is?converted?to?Windows?format.
((?CYGWIN?))?&&?CLASSPATH=$(cygpath?--path?--mixed?"${CLASSPATH}")
#?Launch?mode
if?[?"x$DAEMON_MODE"?=?"xtrue"?];?then
??nohup?"$JAVA"?$KAFKA_HEAP_OPTS?$KAFKA_JVM_PERFORMANCE_OPTS?$KAFKA_GC_LOG_OPTS?$KAFKA_JMX_OPTS?$KAFKA_LOG4J_OPTS?-cp?"$CLASSPATH"?$KAFKA_OPTS?"$@"?>?"$CONSOLE_OUTPUT_FILE"?2>&1?<?/dev/null?&
else
??exec?"$JAVA"?$KAFKA_HEAP_OPTS?$KAFKA_JVM_PERFORMANCE_OPTS?$KAFKA_GC_LOG_OPTS?$KAFKA_JMX_OPTS?$KAFKA_LOG4J_OPTS?-cp?"$CLASSPATH"?$KAFKA_OPTS?"$@"
fi
腳本內(nèi)容很長,但是實際上只有最后一部分才是真正在完成啟動操作:
#?Launch?mode
if?[?"x$DAEMON_MODE"?=?"xtrue"?];?then
??nohup?"$JAVA"?$KAFKA_HEAP_OPTS?$KAFKA_JVM_PERFORMANCE_OPTS?$KAFKA_GC_LOG_OPTS?$KAFKA_JMX_OPTS?$KAFKA_LOG4J_OPTS?-cp?"$CLASSPATH"?$KAFKA_OPTS?"$@"?>?"$CONSOLE_OUTPUT_FILE"?2>&1?<?/dev/null?&
else
??exec?"$JAVA"?$KAFKA_HEAP_OPTS?$KAFKA_JVM_PERFORMANCE_OPTS?$KAFKA_GC_LOG_OPTS?$KAFKA_JMX_OPTS?$KAFKA_LOG4J_OPTS?-cp?"$CLASSPATH"?$KAFKA_OPTS?"$@"
fi
Launch modes
在腳本最后一段是有關(guān)啟動方式的提示。
#?Launch?mode
if?[?"x$DAEMON_MODE"?=?"xtrue"?];?then
??nohup?"$JAVA"?$KAFKA_HEAP_OPTS?$KAFKA_JVM_PERFORMANCE_OPTS?$KAFKA_GC_LOG_OPTS?$KAFKA_JMX_OPTS?$KAFKA_LOG4J_OPTS?-cp?"$CLASSPATH"?$KAFKA_OPTS?"$@"?>?"$CONSOLE_OUTPUT_FILE"?2>&1?<?/dev/null?&
else
??exec?"$JAVA"?$KAFKA_HEAP_OPTS?$KAFKA_JVM_PERFORMANCE_OPTS?$KAFKA_GC_LOG_OPTS?$KAFKA_JMX_OPTS?$KAFKA_LOG4J_OPTS?-cp?"$CLASSPATH"?$KAFKA_OPTS?"$@"
fi
這段腳本說明了之前的一大堆腳本都是為了這里啟動賦值進(jìn)行的一系列操作,這里根據(jù)傳遞參數(shù)判斷是否守護(hù)進(jìn)程的方式啟動。這里以使用比較多的 守護(hù)進(jìn)程啟動方式進(jìn)行參數(shù)介紹(實際上兩者差別不算很大)。
KAFKA_HEAP_OPTS
KAFKA_HEAP_OPTS 出自最開頭,判斷堆參數(shù) KAFKA_HEAP_OPTS是否為空,為空就默認(rèn)給它賦值為 "-Xmx1G -Xms1G"。
KAFKA_JVM_PERFORMANCE_OPTS
這個值代表了JVM的啟動參數(shù)。
#?JVM?performance?options
#?MaxInlineLevel=15?is?the?default?since?JDK?14?and?can?be?removed?once?older?JDKs?are?no?longer?supported
#?MaxInlineLevel=15?是自JDK?14以來的默認(rèn)值,一旦舊的JDK不再支持,就可以刪除。
if?[?-z?"$KAFKA_JVM_PERFORMANCE_OPTS"?];?then
??KAFKA_JVM_PERFORMANCE_OPTS="-server?-XX:+UseG1GC?-XX:MaxGCPauseMillis=20?-XX:InitiatingHeapOccupancyPercent=35?-XX:+ExplicitGCInvokesConcurrent?-XX:MaxInlineLevel=15?-Djava.awt.headless=true"
fi
while?[?$#?-gt?0?];?do
??COMMAND=$1
??case?$COMMAND?in
????-name)
??????DAEMON_NAME=$2
??????CONSOLE_OUTPUT_FILE=$LOG_DIR/$DAEMON_NAME.out
??????shift?2
??????;;
????-loggc)
??????if?[?-z?"$KAFKA_GC_LOG_OPTS"?];?then
????????GC_LOG_ENABLED="true"
??????fi
??????shift
??????;;
????-daemon)
??????DAEMON_MODE="true"
??????shift
??????;;
????*)
??????break
??????;;
??esac
done
G1垃圾收集器
Kafka默認(rèn)使用G1的垃圾收集器,本身最低JDK版本要求就是JDK1.8。
-server?-XX:+UseG1GC?-XX:MaxGCPauseMillis=20?-XX:InitiatingHeapOccupancyPercent=35?-XX:+ExplicitGCInvokesConcurrent?-XX:MaxInlineLevel=15?-Djava.awt.headless=true
MaxGCPauseMillis
-XX:MaxGCPauseMillis
:GC最大的停頓毫秒數(shù),暫停時間默認(rèn)值200ms,如果設(shè)置比這個小的值,G1收集器會盡可能的達(dá)到這個預(yù)期設(shè)置。
因為Kafka是非常激進(jìn)的高并發(fā)分布式消息隊列,為了獲取更高的并發(fā),使用20ms的極限值值盡可能的減少GC時間,最后用極短GC的代價換取高吞吐,當(dāng)然結(jié)果會導(dǎo)致垃圾回收不干凈。
但Kafka對于JVM本身的堆內(nèi)存占用并不是很多,默認(rèn)20ms的停頓時間其實是可以放心使用的。
此外從Kafka的設(shè)計來看,更頻繁的GC是為了盡可能的觸發(fā)Full Gc,因為Full Gc是回收Direct Memory的條件,而Kafka大量使用了頁緩存提高數(shù)據(jù)的Log的讀寫速度,底層用的也是Java的Direct Memory。
InitiatingHeapOccupancyPercent
這個參數(shù)實際上出入比較大,根據(jù)源碼分析在JDK8b12版本之后,以及JDK11 之前這個參數(shù)和官方的文檔描述,這個值的含義是符合“整堆”來計算是否觸發(fā)Mixed Gc,但是JDK8b12版本之后更高的補(bǔ)丁,以及JDK11之后就變了,它變成根據(jù)老年代占整堆的比重。
這樣的出入問題源自此參數(shù)的源碼BUG,這部分涉及源碼的探討就不討論了,具體可以看關(guān)于G1收集器參數(shù)InitiatingHeapOccupancyPercent的正確認(rèn)知 - 豆大俠的菜地 (doudaxia.club)這篇大佬的文章分析。
這里直接給出結(jié)論:
如果你使用的JDK版本在8b12之前,XX:InitiatingHeapOccupancyPercent是整個堆使用量與堆總體容量的比值;
如果你使用的JDK版本在8b12之后(包括大版本9、10、11....),那么
XX:InitiatingHeapOccupancyPercent
是老年代大小與堆總體容量的比值這種說法和修改之后的JVM源碼符合。
整體算是一個隱藏已久的BUG,因為G1的垃圾收集器設(shè)計角度看,它更關(guān)心的是Old Region
占滿整個堆空間之前提前盡可能的進(jìn)行回收,而不是簡單的看看剩余空間在整個堆空間的占比,因為剩余空間不是一個十分可靠的衡量值。
為了驗證上文大佬的說法,個人也去參閱JDK8的Oracle文檔:java (oracle.com)
-XX:c=percent
????Sets?the?percentage?of?the?heap?occupancy?(0?to?100)?at?which?to?start?a?concurrent?GC?cycle.?It?is?used?by?garbage?collectors?that?trigger?a?concurrent?GC?cycle?based?on?the?occupancy?of?the?entire?heap,?not?just?one?of?the?generations?(for?example,?the?G1?garbage?collector).
????By?default,?the?initiating?value?is?set?to?45%.?A?value?of?0?implies?nonstop?GC?cycles.?The?following?example?shows?how?to?set?the?initiating?heap?occupancy?to?75%:
????-XX:InitiatingHeapOccupancyPercent=75
關(guān)鍵字 entire heap,也就是簡單的剩余空間和整堆的占比。這里同樣接著翻閱了一下,直到JDK12版本,這個描述還是和JDK8的版本一致的。直到閱讀長期支持的JDK17的文檔,發(fā)現(xiàn)里面的說法終于變了:
Garbage-First (G1) Garbage Collector (oracle.com
XX:InitiatingHeapOccupancyPercent
determines the initial value as a percentage of the size of the current old generation as long as there aren't enough observations to make a good prediction of the Initiating Heap Occupancy threshold. Turn off this behavior of G1 using the option-XX:-G1UseAdaptiveIHOP
. In this case, the value of-XX:InitiatingHeapOccupancyPercent
always determines this threshold.。 “XX:啟動堆占用百分比”將初始值確定為當(dāng)前老一代大小的百分比,只要沒有足夠的觀測值來很好地預(yù)測起始堆占用閾值。 使用選項'-XX:-G1UseAdaptiveIHOP
'關(guān)閉G1的此行為。在這種情況下-XX:InitiatingHeapOccupancyPercent
?啟動堆占用百分比'的值始終確定此閾值。
所以這個值的真實含義和使用的JDK版本有關(guān),并且JDK8的后續(xù)補(bǔ)丁版本也修復(fù)了這個問題,所以最終建議是升級JDK8的補(bǔ)丁版本,或者使用JDK11之后的版本。
-XX:+ExplicitGCInvokesConcurrent
看似簡單的參數(shù),實際上又是隱藏這非常多的“坑”和細(xì)節(jié),這里我們劃分更多的小節(jié)慢慢細(xì)品。
簡單理解
這個參數(shù)是指通過使用System.gc()
請求啟用并發(fā) GC 的調(diào)用,默認(rèn)禁用。如果沒有特殊的應(yīng)用場景,大部分情況下這個參數(shù)都是被建議禁用的,而并發(fā)GC實際上就是CMS的并發(fā)回收處理。
個人在官方文檔中搜到類似的參數(shù)描述:Garbage-First Garbage Collector Tuning (oracle.com)。
Other causes than Allocation Failure for a Full GC typically indicate that either the application or some external tool causes a full heap collection. If the cause is , and there is no way to modify the application sources, the effect of Full GCs can be mitigated by using or let the VM completely ignore them by setting . External tools may still force Full GCs; they can be removed only by not requesting them.System.gc()-XX:+ExplicitGCInvokesConcurrent -XX:+DisableExplicitGC
上面一大段的話大意指的是:阻止外部調(diào)用Full GC(也就是System.gc()
)要么直接設(shè)置-XX:+DisableExplicitGC
,要么設(shè)置-XX:+ExplicitGCInvokesConcurrent
提高強(qiáng)制Full Gc的效率,閱讀源碼發(fā)現(xiàn)這兩個參數(shù)不能一起開啟,因為-XX:+ExplicitGCInvokesConcurrent
需要關(guān)閉-XX:+DisableExplicitGC
參數(shù)才能生效。
部分文章也解釋僅僅建議在G1的垃圾收集器中可以使用
-XX:+ExplicitGCInvokesConcurrent
。其他垃圾收集器不建議使用。
Kafka官方修復(fù)BUG:-XX:+DisableExplicitGC 改為 -XX:+ExplicitGCInvokesConcurrent
為什么兩者只能選其一使用,JDK 8 的JVM中存在類似的代碼可以給予解釋。
bool?GenCollectedHeap::should_do_concurrent_full_gc(GCCause::Cause?cause)?{
????//?檢查參數(shù)?-XX:+DisableExplicitGC?和?-XX:+ExplicitGCInvokesConcurrent
??return?UseConcMarkSweepGC?&&
?????????((cause?==?GCCause::_gc_locker?&&?GCLockerInvokesConcurrent)?||
?????????//?-XX:+ExplicitGCInvokesConcurrent?需要滿足不配置-XX:+DisableExplicitGC的條件,才能判定為true
??????????(cause?==?GCCause::_java_lang_system_gc?&&?ExplicitGCInvokesConcurrent));
}
void?GenCollectedHeap::collect(GCCause::Cause?cause)?{
????//?檢查參數(shù)?-XX:+DisableExplicitGC?和?-XX:+ExplicitGCInvokesConcurrent
??if?(should_do_concurrent_full_gc(cause))?{
????//?mostly?concurrent?full?collection
????collect_mostly_concurrent(cause);
????ShouldNotReachHere();
??}?else?{
#ifdef?ASSERT
????if?(cause?==?GCCause::_scavenge_alot)?{
??????//?minor?collection?only
??????collect(cause,?0);
????}?else?{
??????//?Stop-the-world?full?collection
??????//?STW?進(jìn)行Full?Gc
??????collect(cause,?n_gens()?-?1);
????}
????//?Stop-the-world?full?collection
????collect(cause,?n_gens()?-?1);
??}
}
collect里一開頭就有個判斷,如果should_do_concurrent_full_gc
返回true,那會執(zhí)行collect_mostly_concurrent做并行的回收。
回到Kafka的服務(wù)端參數(shù),KafKa最初的服務(wù)端啟動腳本中,此參數(shù)實際為-XX:+DisableExplicitGC
,但是后續(xù)被指出會影響直接內(nèi)存的回收性能,并且很可能會導(dǎo)致直接內(nèi)存無法被回收!
為什么會有這么嚴(yán)重 ? 這里先不急著分析,而是先看看作者的這個issue的提交:
KAFKA-5470: Replace -XX:+DisableExplicitGC with -XX:+ExplicitGCInvokesConcurrent in kafka-run-class by ijuma · Pull Request #3371 · apache/kafka (github.com)
提交者的原話是:
This is important because Bits.reserveMemory calls System.gc() hoping to free native memory in order to avoid throwing an OutOfMemoryException. This call is currently a no-op due to -XX:+DisableExplicitGC.
It's worth mentioning that -XX:MaxDirectMemorySize can be used to increase the amount of native memory available for allocation of direct byte buffers.
簡單來說就是Bits.reserveMemory里面會有System.gc()
調(diào)用,通過程序強(qiáng)制調(diào)用Full Gc來回收掉native內(nèi)存,所以建議在JVM參數(shù)中刪掉-XX:+DisableExplicitGC
,開啟System.gc();
并且通過添加-XX:+ExplicitGCInvokesConcurrent
讓System.gc()
調(diào)用效率更高一些。
另外大佬這里還提了一嘴
-XX:MaxDirectMemorySize
可以用來提高可用于分配直接字節(jié)緩沖區(qū)的本地內(nèi)存的數(shù)量。 大佬一句話就是一個知識點,牛呀。
Bits#reserveMemory
既然提交者提到了Bits#reserveMemory
,這里就順帶貼一下官方j(luò)dk8的java.nio.Bits#reserveMemory源碼方便理解:
//?These?methods?should?be?called?whenever?direct?memory?is?allocated?or
//?freed.??They?allow?the?user?to?control?the?amount?of?direct?memory
//?which?a?process?may?access.??All?sizes?are?specified?in?bytes.
//每當(dāng)分配直接內(nèi)存或釋放。?它們允許用戶控制直接內(nèi)存的數(shù)量進(jìn)程可以訪問的內(nèi)容。?所有大小均以字節(jié)為單位指定。
static?void?reserveMemory(long?size,?int?cap)?{
????if?(!memoryLimitSet?&&?VM.isBooted())?{
????????maxMemory?=?VM.maxDirectMemory();
????????memoryLimitSet?=?true;
????}
????//?optimist!
????if?(tryReserveMemory(size,?cap))?{
????????return;
????}
????final?JavaLangRefAccess?jlra?=?SharedSecrets.getJavaLangRefAccess();
????//?retry?while?helping?enqueue?pending?Reference?objects
????//?which?includes?executing?pending?Cleaner(s)?which?includes
????//?Cleaner(s)?that?free?direct?buffer?memory
????while?(jlra.tryHandlePendingReference())?{
????????if?(tryReserveMemory(size,?cap))?{
????????????return;
????????}
????}
????//?trigger?VM's?Reference?processing
????System.gc();
????//?a?retry?loop?with?exponential?back-off?delays
????//?(this?gives?VM?some?time?to?do?it's?job)
????boolean?interrupted?=?false;
????try?{
????????long?sleepTime?=?1;
????????int?sleeps?=?0;
????????while?(true)?{
????????????if?(tryReserveMemory(size,?cap))?{
????????????????return;
????????????}
????????????if?(sleeps?>=?MAX_SLEEPS)?{
????????????????break;
????????????}
????????????if?(!jlra.tryHandlePendingReference())?{
????????????????try?{
????????????????????Thread.sleep(sleepTime);
????????????????????sleepTime?<<=?1;
????????????????????sleeps++;
????????????????}?catch?(InterruptedException?e)?{
????????????????????interrupted?=?true;
????????????????}
????????????}
????????}
????????//?no?luck
????????throw?new?OutOfMemoryError("Direct?buffer?memory");
????}?finally?{
????????if?(interrupted)?{
????????????//?don't?swallow?interrupts
????????????Thread.currentThread().interrupt();
????????}
????}
}
我們通過閱讀JDK8的Nio包的這部分用于分配DirectMememory的一段代碼,發(fā)現(xiàn)每次Direct Mememory進(jìn)行實際的分配動作之前,都會調(diào)用這個方法檢測是否有足夠空間分配時都被調(diào)用,不過里面的邏輯奇奇怪怪的,初看確實有點摸不著頭腦。
國外有網(wǎng)友直接痛罵了這一段代碼是一坨Shit:java.nio.Bits.reserveMemory uses a lock, calls System.gc, and is generally bad code... (google.com)
1.??ALL?memory?access?requires?a?lock.??That's?evil?if?you're?allocating?small?chunks.
2.??The?code?to?change?the?reserved?memory?counters?is?duplicated?twice.??This?is?a?great?way?to?introduce?bugs.??(how?did?this?even?get?approved??do?they?not?do?code?audits?or?require?that?commits?be?approved?)
3.??If?you?are?out?of?memory?we?call?System.gc...?EVIL.??The?entire?way?direct?memory?is?reclaimed?via?GC?is?a?horrible?design.
4.??After?GC?they?sleep?100ms.??What's?that?about???Why?100ms???Why?not?1ms???
所有的內(nèi)存訪問都需要一個鎖。 如果你分配的是小塊的內(nèi)存簡直就是噩夢。
改變保留內(nèi)存計數(shù)器的代碼重復(fù)了兩次。 這是個引入錯誤的好方法。 (難道他們不進(jìn)行代碼審計或要求提交的代碼必須得到批準(zhǔn)嗎?)
如果你沒有內(nèi)存了,我們就調(diào)用System.gc... ?通過GC回收直接內(nèi)存的整個方式是一個可怕的設(shè)計。
在GC之后,他們會休眠100ms。 那是什么意思? 為什么是100ms? 為什么不是1ms?
個人并不感冒這些評論,這里拎出System.gc()
這行代碼來分析具體意圖。要看懂這一行代碼的意圖,我們需要了解DirectMemory關(guān)聯(lián)的本機(jī)內(nèi)存是如何清理的,這里就直接給出答案了。
JVM實際上是管不到DirectMemory的,需要依靠特殊的方式回收掉DirectMemory:
手動調(diào)用
unsafe.freeMemory()
進(jìn)行釋放,netty中ByteBuf.release()
就是這種方式實現(xiàn)的;利用GC機(jī)制在GC的過程中自動調(diào)用
unsafe.freeMemory()
釋放被引用的直接內(nèi)存;
這段代碼作者的意圖明顯是顯示調(diào)用System.gc()
,盡可能回收不可達(dá)的DirectByteBuffer
對象,也只有通過GC才會自動觸發(fā)unsafe.freeMemory()
的調(diào)用,釋放直接內(nèi)存。
至于其他代碼.....這里不做過多評論。
Fix -XX:+DisableExplicitGC
基于以上種種原因,Kafka官方最終提交了一個Commit修復(fù)這個問題:
Fix run class to work with Java 10 and use ExplicitGCInvokesConcurrent by ijuma · Pull Request #1329 · confluentinc/ksql (github.com)
具體的調(diào)整細(xì)節(jié)可以看下面的連接,讀者可以通過對比自己的下載Kafka啟動腳本查看是否修復(fù)這個問題:
KAFKA-5470: Replace -XX:+DisableExplicitGC with -XX:+ExplicitGCInvokesConcurrent in kafka-run-class by ijuma · Pull Request #3371 · apache/kafka (github.com)
其他參考資料
下面的這些參考資料可以幫助我們更深入的理解-XX:+ExplicitGCInvokesConcurrent
參數(shù)附帶的知識點:
-XX:+ExplicitGCInvokesConcurrent的含義:What is JVM startup parameter: -XX:+ExplicitGCInvokesConcurrent? - yCrash Answers
官方的G1文檔:Java HotSpot Garbage Collection (oracle.com)
為什么僅限G1可以開啟此參數(shù)來進(jìn)行健康檢查,其他垃圾收集器建議關(guān)閉此參數(shù):Health Check: Explicit Garbage Collection | Jira | Atlassian Documentation
JVM源碼分析之SystemGC完全解讀 | HeapDump性能社區(qū)
為什么不能同時設(shè)置
-XX:+DisableExplicitGC
以及-XX:+ExplicitGCInvokesConcurrent
為什么CMS GC下-XX:+ExplicitGCInvokesConcurrent這個參數(shù)加了之后會比真正的Full GC好?
它如何做到暫停整個進(jìn)程?
堆外內(nèi)存分配為什么有時候要配合System.gc?
Netty回收堆外內(nèi)存的策略又是如何?
小結(jié)
筆者也沒有想到一個簡單的參數(shù)能牽扯出這么多內(nèi)容,這里做一個大概的總結(jié):
Kafka官方曾經(jīng)禁用過
System.gc()
。后面有大神分析了腳本和JDK的NIO源碼,發(fā)現(xiàn)禁用
System.gc()
這不是有問題嘛,你Kafka大量使用Java的直接內(nèi)存,直接內(nèi)存靠一般的Gc是回收不掉的,只能靠Ful Gc順帶回收,JDK官方代碼又是靠頻繁調(diào)用System.gc()
強(qiáng)制騰出直接內(nèi)存空間的,你System.gc()
禁用了不是“找死”么,于是趕緊解釋了一波Bits#reserveMemory
寫的“垃圾代碼”來證實自己的觀點,然后建議啟用System.gc()
,并且為了提高Full Gc效率使用-XX:+ExplicitGCInvokesConcurrent
。官方發(fā)現(xiàn)這個問題趕緊修復(fù)了一版并且提交了issue。
MaxInlineLevel
java
有一個參數(shù) -XX:MaxInlineLevel
(JDK14之前默認(rèn)值為 9),這個值在JDK14之后默認(rèn)值改為15。這個值的修改可以參考JDK官方的聲明 https://bugs.openjdk.org/browse/JDK-8234863。
下面的圖Oracle官方對于JDK14版本之后修改
MaxInlineLevel=15
的Push。

下面長篇大論源自網(wǎng)上收集的資料和個人理解,其實簡單理解為現(xiàn)代硬件資源足以支持 -XX:MaxInlineLevel
設(shè)置為15,更大的內(nèi)聯(lián)深度可以讓JIT編譯出更多的本地代碼從而提高Java代碼的運(yùn)行效率即可。
如果你的服務(wù)器還是古舊的四五年前的機(jī)器,或者生產(chǎn)機(jī)器確實渣的可以,那么還是建議把這個參數(shù)
-XX:MaxInlineLevel
改回 9 比較妥當(dāng)。
長篇大論的部分:
鏈接https://bugs.openjdk.org/browse/JDK-8234863的聲明指出,15這個值在scala上的性能測試是被認(rèn)為最優(yōu)結(jié)果。這個值在現(xiàn)代處理器速度以及性能優(yōu)化較好的今天最為合適,默認(rèn)值9這個數(shù)字顯得非常過時。
Kafka作為激進(jìn)壓榨機(jī)器性能的典范,也遵從JDK官方的改動默認(rèn)所有版本的JDK統(tǒng)一使用15這個默認(rèn)值。
這里額外插一嘴,個人認(rèn)為實際上這個值Oracle官方在JDK11就可以修改為15。
MaxInlineLevel本身的判斷邏輯似乎更引起廣大程序員的關(guān)注,StackFlow上有一篇關(guān)于這個參數(shù)的討論:https://stackoverflow.com/questions/32503669/why-does-the-jvm-have-a-maximum-inline-depth 比較有意思。
在評論中有網(wǎng)友指出在比較低的JDK8版本當(dāng)中,MaxRecursiveInlineLevel對直接和間接的遞歸調(diào)用都進(jìn)行計數(shù),編譯后的代碼應(yīng)該在運(yùn)行時保持對整個內(nèi)聯(lián)樹的跟蹤(以便能夠解壓和去優(yōu)化)。緊接著是其他人的一些個人觀點,沒人接這個人話茬=-=,尷尬。
繼續(xù)翻閱,下面的評論有一位大佬解釋了為什么會出現(xiàn)MaxInlineLevel這個參數(shù),簡單易懂這里就直接貼過來了:
One reason is also that the inlining itself in the HotSpot JVM is implemented with recursion. Every time inlining of a method is started a new context is created on the native stack. Allowing an unlimited depth would eventually make the JIT-compiler crash when it runs out of stack.
(舊版保守的限制方法內(nèi)聯(lián)深度),其中一個原因是HotSpot JVM的內(nèi)聯(lián)本身是用遞歸實現(xiàn)的。每次對一個方法進(jìn)行內(nèi)聯(lián)時,都會在本地堆棧中創(chuàng)建一個新的上下文。如果允許無限的深度,最終會使JIT-編譯器在堆棧耗盡時崩潰。
在過去硬件資源緊張的情況下,過度的方法內(nèi)聯(lián)有可能會出現(xiàn)比較深的堆棧調(diào)用,十分消耗程序內(nèi)存,但是現(xiàn)代內(nèi)存動不動就是32,64,128G 的今天,加上處理器的核心數(shù)量上來了之后,擴(kuò)大默認(rèn)的方法內(nèi)聯(lián)深度參數(shù)值確實非常有必要。
方法內(nèi)聯(lián)是JVM比較底層的優(yōu)化,可以通過周大神的《深入理解JVM虛擬機(jī)第三版》了解。
如果不懂方法內(nèi)聯(lián)直接無腦設(shè)置MaxInlineLevel=15
即可,沒有為什么,官方都已經(jīng)在高版本JDK修改了默認(rèn)值,JDK8忠實粉絲自然也可以這么干。
jdk14 hotspot 依賴的調(diào)整日志:https://hg.openjdk.org/jdk8u/jdk8u/hotspot/
-Djava.awt.headless=true
這個參數(shù)比較奇怪,但是實際上在SpringBoot源碼中也有同樣的寫法。
這算是一個不太被關(guān)注的優(yōu)化參數(shù),簡單理解是-Djava.awt.headless=true
可以屏蔽掉一些不必要的外置設(shè)備影響,告知程序當(dāng)前沒有外置設(shè)備,盡可能的讓程序底層自己模擬,比如打印從圖形顯示變?yōu)榭刂婆_打印。
又是牽扯內(nèi)容很多的一個點,具體解釋可以看這篇文章:[[【Java】The Java Headless Mode]],篇幅有限,這里就不多解釋了。
KAFKA_GC_LOG_OPTS
見名知意,就是JVM的日志參數(shù)配置,Kafka最終的日志格式為:XXX-gc.log
,日志配置這一塊和大部分以JAVA為底層的開源組件大差不差,簡單的掃一眼差不多了。
#?Log?directory?to?use
#?獲取log_dir,如果沒配置就那?$base_dir?環(huán)境變量
if?[?"x$LOG_DIR"?=?"x"?];?then
??#?base_dir=$(dirname?$0)/..
??LOG_DIR="$base_dir/logs"
fi
#?GC?options?
GC_FILE_SUFFIX='-gc.log'
GC_LOG_FILE_NAME=''
if?[?"x$GC_LOG_ENABLED"?=?"xtrue"?];?then
??GC_LOG_FILE_NAME=$DAEMON_NAME$GC_FILE_SUFFIX
??#?The?first?segment?of?the?version?number,?which?is?'1'?for?releases?before?Java?9
??#?it?then?becomes?'9',?'10',?...
??#?Some?examples?of?the?first?line?of?`java?--version`:
??#?8?->?java?version?"1.8.0_152"
??#?9.0.4?->?java?version?"9.0.4"
??#?10?->?java?version?"10"?2018-03-20
??#?10.0.1?->?java?version?"10.0.1"?2018-04-17
??#?We?need?to?match?to?the?end?of?the?line?to?prevent?sed?from?printing?the?characters?that?do?not?match
??JAVA_MAJOR_VERSION=$("$JAVA"?-version?2>&1?|?sed?-E?-n?'s/.*?version?"([0-9]*).*$/\1/p')
??if?[[?"$JAVA_MAJOR_VERSION"?-ge?"9"?]]?;?then
????KAFKA_GC_LOG_OPTS="-Xlog:gc*:file=$LOG_DIR/$GC_LOG_FILE_NAME:time,tags:filecount=10,filesize=100M"
??else
????KAFKA_GC_LOG_OPTS="-Xloggc:$LOG_DIR/$GC_LOG_FILE_NAME?-verbose:gc?-XX:+PrintGCDetails?-XX:+PrintGCDateStamps?-XX:+PrintGCTimeStamps?-XX:+UseGCLogFileRotation?-XX:NumberOfGCLogFiles=10?-XX:GCLogFileSize=100M"
??fi
fi
JAVA_MAJOR_VERSION就是通過正則去除JDK的主版本號。
如果主版本號大于或者等于JDK9,就使用JDK9新增的
-xlog:gc*
統(tǒng)一的日志參數(shù)作為啟動參數(shù),如果是JDK8之前的版本,就需要用一大堆舊版的日志參數(shù),學(xué)習(xí)和使用成本比較大:
-verbose:gc -XX:+PrintGCDetails
這兩個參數(shù)經(jīng)常在低版本JDK一起出現(xiàn),最大的區(qū)別是前者是穩(wěn)定版本,后者則是被認(rèn)為是不穩(wěn)定的日志啟動參數(shù)(強(qiáng)制和其他GC參數(shù)配合出現(xiàn)顯得不穩(wěn)定)。-XX:+PrintGCDateStamps
:每行開頭顯示當(dāng)前絕對的日期及時間,打印GC發(fā)生時的時間戳,搭配 -XX:+PrintGCDetails 使用,不可以獨(dú)立使用。-XX:+PrintGCTimeStamps
自從JVM啟動以來的時間。GCLogFileSize=100M,限制GC日志文件大小為100M。
NumberOfGCLogFiles=10,允許存在的GC日志文件數(shù)量為10個。
UseGCLogFileRotation,讓GC日志不斷循環(huán),如果最后一個GC日志寫滿,將會從第一個文件重新開始寫入
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
是舊版本混亂的GC參數(shù)配置誕生的惡果,這些參數(shù)在JDK9之后被統(tǒng)統(tǒng)被-xlog:gc*
替代。
-XX:+PrintGCDateStamps
和-XX:+PrintGCTimeStamps
可以直接看下面的例子對比:
-XX:+PrintGCDateStamps
日志輸出示例:
2014-01-03T12:08:38.102-0100:?[GC?66048K->53077K(251392K),?0,0959470?secs]
2014-01-03T12:08:38.239-0100:?[GC?119125K->114661K(317440K),?0,1421720?secs]
-XX:+PrintGCTimeStamps
日志輸出示例:
0,185:?[GC?66048K->53077K(251392K),?0,0977580?secs]
0,323:?[GC?119125K->114661K(317440K),?0,1448850?secs]
因為
-XX:+PrintGCDetails
被標(biāo)記為manageable,所以可以通過如下三種方式修改: 1、com.sun.management.HotSpotDiagnosticMXBean API 2、JConsole 3、jinfo -flag
最后再把英文注釋部分簡單翻譯一下:
第一個參數(shù)如果是1開頭,代表是JDK9之后的版本。
java --version
產(chǎn)生的結(jié)果如下:8 -> java version "1.8.0_152"
9.0.4 -> java version "9.0.4"
10 -> java version "10" 2018-03-20
10.0.1 -> java version "10.0.1" 2018-04-17
通過正則表達(dá)式匹配到行尾,以防止sed打印出不匹配的字符
KAFKA_JMX_OPTS
JMX全稱Java Management Extensions, 為Java應(yīng)用提供管理擴(kuò)展功能。在JDK 5的時候引入,Kafka設(shè)置啟動參數(shù)讓Kafka應(yīng)用程序獲得JMX遠(yuǎn)程調(diào)用的支持。
#?JMX?settings
if?[?-z?"$KAFKA_JMX_OPTS"?];?then
??KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote?-Dcom.sun.management.jmxremote.authenticate=false??-Dcom.sun.management.jmxremote.ssl=false?"
fi
KAFKA_JMX_OPTS對應(yīng)的value的含義參考自:https://www.jianshu.com/p/414647c1179e,此處列舉一些有關(guān)JMX的相關(guān)參數(shù):
參數(shù)名類型描述-Dcom.sun.management.jmxremote布爾是否支持遠(yuǎn)程JMX訪問,默認(rèn)true-Dcom.sun.management.jmxremote.port數(shù)值監(jiān)聽端口號,方便遠(yuǎn)程訪問-Dcom.sun.management.jmxremote.authenticate布爾是否需要開啟用戶認(rèn)證,默認(rèn)開啟-Dcom.sun.management.jmxremote.ssl布爾是否對連接開啟SSL加密,默認(rèn)開啟-Dcom.sun.management.jmxremote.access.file路徑對訪問用戶的權(quán)限授權(quán)的文件的路徑,默認(rèn)路徑JRE_HOME/lib/management/jmxremote.access
-Dcom.sun.management.jmxremote. password.file路徑設(shè)置訪問用戶的用戶名和密碼,默認(rèn)路徑JRE_HOME/lib/management/ jmxremote.password
KAFKA_LOG4J_OPTS
log4j的日志配置地址。
if?[?"x$KAFKA_LOG4J_OPTS"?=?"x"?];?then??
????export?KAFKA_LOG4J_OPTS="-Dlog4j.configuration=file:$base_dir/../config/log4j.properties"??
fi??
配置含義不需要記憶,在閱讀的時候查閱相關(guān)資料即可:https://www.jianshu.com/p/ccafda45bcea,這里直接貼過來作為注釋部分供讀者參考。
#?根Log
#?默認(rèn)日志等級為INFO級別
#?NFO、WARN、ERROR和FATAL級別的日志信息都會輸出
#?日志最終輸出到kafkaAppender
log4j.rootLogger=INFO,?stdout,?kafkaAppender??
#?控制臺配置
log4j.appender.stdout=org.apache.log4j.ConsoleAppender??
#?布局模式使用可以靈活模式
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout??
#?日志打印格式
#?[%d]?輸出日志時間點的日期或時間,默認(rèn)格式為ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd?HH:mm:ss,SSS}。
#?%m::輸出代碼中指定的具體日志信息。
#?%p:輸出日志信息的優(yōu)先級,即DEBUG,INFO,WARN,ERROR,F(xiàn)ATAL。
#?%c:輸出日志信息所屬的類目,通常就是所在類的全名。
#?%n:輸出一個回車換行符,Windows平臺為"\r\n",Unix平臺為"\n"。
log4j.appender.stdout.layout.ConversionPattern=[%d]?%p?%m?(%c)%n??
#?DailyRollingFileAppender?Kafka默認(rèn)服務(wù)端日志
log4j.appender.kafkaAppender=org.apache.log4j.DailyRollingFileAppender??
log4j.appender.kafkaAppender.DatePattern='.'yyyy-MM-dd-HH??
#?server.log?存儲位置
log4j.appender.kafkaAppender.File=${kafka.logs.dir}/server.log??
log4j.appender.kafkaAppender.layout=org.apache.log4j.PatternLayout??
log4j.appender.kafkaAppender.layout.ConversionPattern=[%d]?%p?%m?(%c)%n??
#?DailyRollingFileAppender?狀態(tài)機(jī)變更日志
log4j.appender.stateChangeAppender=org.apache.log4j.DailyRollingFileAppender??
#?按照每小時產(chǎn)生一個日志的方式
log4j.appender.stateChangeAppender.DatePattern='.'yyyy-MM-dd-HH???
log4j.appender.stateChangeAppender.File=${kafka.logs.dir}/state-change.log??
log4j.appender.stateChangeAppender.layout=org.apache.log4j.PatternLayout??
log4j.appender.stateChangeAppender.layout.ConversionPattern=[%d]?%p?%m?(%c)%n??
#?請求日志
log4j.appender.requestAppender=org.apache.log4j.DailyRollingFileAppender??
log4j.appender.requestAppender.DatePattern='.'yyyy-MM-dd-HH??
log4j.appender.requestAppender.File=${kafka.logs.dir}/kafka-request.log??
log4j.appender.requestAppender.layout=org.apache.log4j.PatternLayout??
log4j.appender.requestAppender.layout.ConversionPattern=[%d]?%p?%m?(%c)%n??
#?Log清理日志
log4j.appender.cleanerAppender=org.apache.log4j.DailyRollingFileAppender??
log4j.appender.cleanerAppender.DatePattern='.'yyyy-MM-dd-HH??
log4j.appender.cleanerAppender.File=${kafka.logs.dir}/log-cleaner.log??
log4j.appender.cleanerAppender.layout=org.apache.log4j.PatternLayout??
log4j.appender.cleanerAppender.layout.ConversionPattern=[%d]?%p?%m?(%c)%n??
#?Controller?日志
log4j.appender.controllerAppender=org.apache.log4j.DailyRollingFileAppender??
log4j.appender.controllerAppender.DatePattern='.'yyyy-MM-dd-HH??
log4j.appender.controllerAppender.File=${kafka.logs.dir}/controller.log??
log4j.appender.controllerAppender.layout=org.apache.log4j.PatternLayout??
log4j.appender.controllerAppender.layout.ConversionPattern=[%d]?%p?%m?(%c)%n??
#?驗證日志
log4j.appender.authorizerAppender=org.apache.log4j.DailyRollingFileAppender??
log4j.appender.authorizerAppender.DatePattern='.'yyyy-MM-dd-HH??
log4j.appender.authorizerAppender.File=${kafka.logs.dir}/kafka-authorizer.log??
log4j.appender.authorizerAppender.layout=org.apache.log4j.PatternLayout??
log4j.appender.authorizerAppender.layout.ConversionPattern=[%d]?%p?%m?(%c)%n??
??
#?Change?the?line?below?to?adjust?ZK?client?logging??
#?修改下面的日志控制ZK的日志輸出
log4j.logger.org.apache.zookeeper=INFO??
??
#?Change?the?two?lines?below?to?adjust?the?general?broker?logging?level?(output?to?server.log?and?stdout)??
#?更改下面兩行以調(diào)整一般代理日志記錄級別(輸出到?server.log?和?stdout)
log4j.logger.kafka=INFO??
log4j.logger.org.apache.kafka=INFO??
??
#?Change?to?DEBUG?or?TRACE?to?enable?request?logging??
#?修改日志級別為?DEBUG和TRACE獲取請求日志
log4j.logger.kafka.request.logger=WARN,?requestAppender??
log4j.additivity.kafka.request.logger=false??
??
#?Uncomment?the?lines?below?and?change?log4j.logger.kafka.network.RequestChannel$?to?TRACE?for?additional?output??
#?取消注釋下面的行并將?log4j.logger.kafka.network.RequestChannel$?更改為?TRACE?以獲得額外的輸出
#?related?to?the?handling?of?requests??
#?與請求的處理相關(guān)
#log4j.logger.kafka.network.Processor=TRACE,?requestAppender??
#log4j.logger.kafka.server.KafkaApis=TRACE,?requestAppender??
#log4j.additivity.kafka.server.KafkaApis=false??
log4j.logger.kafka.network.RequestChannel$=WARN,?requestAppender??
log4j.additivity.kafka.network.RequestChannel$=false??
??
log4j.logger.kafka.controller=TRACE,?controllerAppender??
log4j.additivity.kafka.controller=false??
??
log4j.logger.kafka.log.LogCleaner=INFO,?cleanerAppender??
log4j.additivity.kafka.log.LogCleaner=false??
??
log4j.logger.state.change.logger=INFO,?stateChangeAppender??
log4j.additivity.state.change.logger=false??
??
#?Access?denials?are?logged?at?INFO?level,?change?to?DEBUG?to?also?log?allowed?accesses?
#?拒絕訪問記錄在?INFO?級別,更改為?DEBUG?以記錄允許的訪問
log4j.logger.kafka.authorizer.logger=INFO,?authorizerAppender??
log4j.additivity.kafka.authorizer.logger=false
KAFKA_OPTS
KAFKA_OPTS 可以在這里設(shè)置自己的想要的通用配置:
#?Generic?jvm?settings?you?want?to?add
if?[?-z?"$KAFKA_OPTS"?];?then
??KAFKA_OPTS=""
fi
UPGRADE_KAFKA_STREAMS_TEST_VERSION
變量名稱翻譯過來是“升級kafka流的測試版本”,這里大致的意思是取出版本號進(jìn)行一些判斷之后設(shè)置到ClassPath當(dāng)中。
說實話這部分內(nèi)容看不太懂,但是不算是十分重要的東西,可以以后深入之后回來了解,這里直接忘記這個設(shè)置即可。
if?[?-z?"$UPGRADE_KAFKA_STREAMS_TEST_VERSION"?];?then
??for?file?in?"$base_dir"/streams/examples/build/libs/kafka-streams-examples*.jar;
??do
????if?should_include_file?"$file";?then
??????CLASSPATH="$CLASSPATH":"$file"
????fi
??done
else
??VERSION_NO_DOTS=`echo?$UPGRADE_KAFKA_STREAMS_TEST_VERSION?|?sed?'s/\.//g'`
??SHORT_VERSION_NO_DOTS=${VERSION_NO_DOTS:0:((${#VERSION_NO_DOTS}?-?1))}?#?remove?last?char,?ie,?bug-fix?number
??for?file?in?"$base_dir"/streams/upgrade-system-tests-$SHORT_VERSION_NO_DOTS/build/libs/kafka-streams-upgrade-system-tests*.jar;
??do
????if?should_include_file?"$file";?then
??????CLASSPATH="$file":"$CLASSPATH"
????fi
??done
??if?[?"$SHORT_VERSION_NO_DOTS"?=?"0100"?];?then
????CLASSPATH="/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs/zkclient-0.8.jar":"$CLASSPATH"
????CLASSPATH="/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs/zookeeper-3.4.6.jar":"$CLASSPATH"
??fi
??if?[?"$SHORT_VERSION_NO_DOTS"?=?"0101"?];?then
????CLASSPATH="/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs/zkclient-0.9.jar":"$CLASSPATH"
????CLASSPATH="/opt/kafka-$UPGRADE_KAFKA_STREAMS_TEST_VERSION/libs/zookeeper-3.4.8.jar":"$CLASSPATH"
??fi
fi
CLASSPATH
運(yùn)行 ./gradlew copyDependantLibs
來獲取本地目錄下的所有依賴性jar。
注意這里劃分了很多個子模塊,所以使用了for循環(huán)加載到CLASSPATH當(dāng)中,這會導(dǎo)致最終產(chǎn)生的命令會非常長。
#?run?./gradlew?copyDependantLibs?to?get?all?dependant?jars?in?a?local?dir
shopt?-s?nullglob
if?[?-z?"$UPGRADE_KAFKA_STREAMS_TEST_VERSION"?];?then
??for?dir?in?"$base_dir"/core/build/dependant-libs-${SCALA_VERSION}*;
??do
????CLASSPATH="$CLASSPATH:$dir/*"
??done
fi
for?file?in?"$base_dir"/examples/build/libs/kafka-examples*.jar;
do
??if?should_include_file?"$file";?then
????CLASSPATH="$CLASSPATH":"$file"
??fi
done
個人嘗試了一下注釋介紹的gradle copyDependantLibs
命令,本地執(zhí)行結(jié)果如下,這個命令會在對應(yīng)的模塊構(gòu)建依賴jar包:
$?gradle?copyDependantLibs
>?Configure?project?:
Building?project?'core'?with?Scala?version?2.13.3
Building?project?'streams-scala'?with?Scala?version?2.13.3
Deprecated?Gradle?features?were?used?in?this?build,?making?it?incompatible?with?Gradle?7.0.
Use?'--warning-mode?all'?to?show?the?individual?deprecation?warnings.
See?https://docs.gradle.org/6.6.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD?SUCCESSFUL?in?2s
55?actionable?tasks:?3?executed,?52?up-to-date
個人是win11的電腦,通過wox查找dependant-libs
結(jié)果如下:

對應(yīng)的一堆依賴jar包

CONSOLE_OUTPUT_FILE
日志的打印輸出地址文件地址設(shè)置,注意不是GC的日志。
??case?$COMMAND?in
????-name)
??????DAEMON_NAME=$2
??????CONSOLE_OUTPUT_FILE=$LOG_DIR/$DAEMON_NAME.out
??????shift?2
??????;;
這里翻閱了一些有關(guān)shift
的資料:
關(guān)于Shift的作用可以參考:https://ss64.com/bash/shift.html。Linux中通過
help shift
查看使用手冊,但是會發(fā)現(xiàn)寫的比較潦草和抽象。
shift:?shift?[n]
????Shift?positional?parameters.
????
????Rename?the?positional?parameters?$N+1,$N+2?...?to?$1,$2?...??If?N?is
????not?given,?it?is?assumed?to?be?1.
????
????Exit?Status:
????Returns?success?unless?N?is?negative?or?greater?than?$#
比如下面的程序:
echo?$1?
echo?$2
shift?1
echo?$1
echo?$2
#?輸出結(jié)果
[zxd@localhost?~]$?./test.sh?1?2?3?5?6
1
2
2
3
shift 1 執(zhí)行之后會彈出第一個參數(shù),之后的運(yùn)行參數(shù)會往前“推動”,變?yōu)?/span>2的值,變?yōu)?/span>3的值,以此類推。
& 后臺啟動和nohup掛起進(jìn)程
末尾部分是設(shè)置ClaassPath,用戶自己自定義參數(shù)以及把標(biāo)準(zhǔn)輸入和輸出重定向到同一個位置,最后就是以后臺模式啟動并且最終通過nohup掛起整個進(jìn)程。
-cp?"$CLASSPATH"?$KAFKA_OPTS?"$@"?>?"$CONSOLE_OUTPUT_FILE"?2>&1?<?/dev/null?&
這段腳本最前面把替代參數(shù)意義進(jìn)行了替換。-cp
是nohup命令的參數(shù),接著是把輸出的結(jié)果全部重定向到標(biāo)準(zhǔn)輸出當(dāng)中,這個地址對應(yīng)CONSOLE_OUTPUT_FILE。
下面理解最后部分的nohup和&
、2>&1
和/dev/null
這幾個常見的服務(wù)端腳本啟動參數(shù)的含義。
nohup和&
nohup:nohup指令會忽略所有掛斷(SIGHUP)信號不掛斷的運(yùn)行。注意nohup命令本身并沒有后臺運(yùn)行的功能,需要配合&
使用。它的實現(xiàn)原理是讓命令不間斷的運(yùn)行實現(xiàn)掛機(jī)的效果。
& 是指在后臺運(yùn)行,但當(dāng)用戶退出(解除掛起)的時候,命令自動也跟著退出,nohup
和&
這兩個指令通常會放到一起使用。
/dev/null
這個空間屬于Linux的一塊特殊空間,UNIX系統(tǒng)中,它被稱為空設(shè)備。以下內(nèi)容摘自維基百科:
/dev/null(或稱空設(shè)備)在類Unix系統(tǒng)中是一個特殊的設(shè)備文件,它丟棄一切寫入其中的數(shù)據(jù)(但報告寫入操作成功),讀取它則會立即得到一個EOF[1]。
在程序員行話,尤其是Unix行話中,/dev/null被稱為比特桶或者黑洞。
2>&1的問題
前面的第一個數(shù)字2通常對應(yīng)下面幾種含義:
0 – stdin (standard input) 標(biāo)準(zhǔn)輸入
1 – stdout (standard output) 標(biāo)準(zhǔn)輸出
2 – stderr (standard error) 標(biāo)準(zhǔn)錯誤輸出
> 是重定向符號,而數(shù)字2的含義是標(biāo)準(zhǔn)錯誤輸出,&1
指的就是標(biāo)準(zhǔn)輸出,三個符號組合到一起就是把標(biāo)準(zhǔn)錯誤輸出輸入重定向到標(biāo)準(zhǔn)輸出當(dāng)中,這里可以理解為“合流”。
注意命令
2>&1
和2>1
是存在區(qū)別的,這里&
不能丟,后者的1代表輸出代表錯誤重定向到一個文件1,不代表標(biāo)準(zhǔn)輸出,只有&1
才代表標(biāo)準(zhǔn)輸出。
如果想要丟棄所有的標(biāo)準(zhǔn)錯誤輸出和標(biāo)準(zhǔn)輸出結(jié)果,下面是一個不錯的例子:
nohup?python3?getfile.py?>?/dev/null?2>&1?&
如果想要寫入到指定的位置,下面是又一個不錯的例子:
nohup?python3?getfile.py?>?test.log?2>&1?&
最后是實際一點的例子:
0?9?\*?\*?\*?/usr/bin/python3?/opt/getFile.py?>?/opt/file.log?2>&1
上面的命令含義是放在crontab中的定時任務(wù),每天9:00啟動這個python的腳本,并把執(zhí)行結(jié)果寫入日志文件file.log中
exec 運(yùn)行
如果不是守護(hù)進(jìn)程的執(zhí)行,則是使用exec
在當(dāng)前的shell中進(jìn)行正常模式啟動,此時整個shell會掛起運(yùn)行kafka服務(wù)端。
??exec?"$JAVA"?$KAFKA_HEAP_OPTS?$KAFKA_JVM_PERFORMANCE_OPTS?$KAFKA_GC_LOG_OPTS?$KAFKA_JMX_OPTS?$KAFKA_LOG4J_OPTS?-cp?"$CLASSPATH"?$KAFKA_OPTS?"$@"
其他內(nèi)容
Lauch modes 使用到的變量設(shè)置包含了啟動整個Kafka服務(wù)端的核心部分,下面再列覺其他的依賴配置以及“輔助”內(nèi)容。
Scala 版本選擇
Kafka是使用Java和Scala混合編寫的,根據(jù)不同的Kafka版本需要不同版本的Scala版本支持,這里官方做了一個版本選擇強(qiáng)制判斷選擇出最合適的Scala。
if?[?-z?"$SCALA_VERSION"?];?then
??SCALA_VERSION=2.13.3
??if?[[?-f?"$base_dir/gradle.properties"?]];?then
????SCALA_VERSION=`grep?"^scalaVersion="?"$base_dir/gradle.properties"?|?cut?-d=?-f?2`
??fi
fi
gradle.properties
Kafka 項目是基于gradle構(gòu)建的,gradle 個人平時基本沒啥接觸機(jī)會,這里做一個大致配置了解。
group=org.apache.kafka ?
# NOTE: When you change this version number, you should also make sure to update ?
# the version numbers in ?
# ?- docs/js/templateData.js ?
# ?- tests/kafkatest/__init__.py ?
# ?- tests/kafkatest/version.py (variable DEV_VERSION) ?
# ?- kafka-merge-pr.py ?
version=2.7.2 ?
scalaVersion=2.13.3 ?
task=build ?
org.gradle.jvmargs=-Xmx2g -Xss4m -XX:+UseParallelGC
gradle這里分配的是2g的堆內(nèi)存,Xss4m每個線程的堆棧大小為4M,最后是使用ParallelGC
垃圾收集器,也是JDK8的默認(rèn)垃圾收集器。
DEBUG模式
如果在啟動參數(shù)里面設(shè)置了KAFKA_DEBUG
,就可以開啟DEBUG模式。
#?Set?Debug?options?if?enabled
if?[?"x$KAFKA_DEBUG"?!=?"x"?];?then
????#?Use?default?ports
????DEFAULT_JAVA_DEBUG_PORT="5005"
????if?[?-z?"$JAVA_DEBUG_PORT"?];?then
????????JAVA_DEBUG_PORT="$DEFAULT_JAVA_DEBUG_PORT"
????fi
????#?Use?the?defaults?if?JAVA_DEBUG_OPTS?was?not?set
????DEFAULT_JAVA_DEBUG_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND_FLAG:-n},address=$JAVA_DEBUG_PORT"
????if?[?-z?"$JAVA_DEBUG_OPTS"?];?then
????????JAVA_DEBUG_OPTS="$DEFAULT_JAVA_DEBUG_OPTS"
????fi
????echo?"Enabling?Java?debug?options:?$JAVA_DEBUG_OPTS"
????KAFKA_OPTS="$JAVA_DEBUG_OPTS?$KAFKA_OPTS"
fi
我們需要了解的是 JAVA_DEBUG_OPTS 命令的含義。起初雖然不是很懂下面的參數(shù)含義,但是可以知道是JAVA調(diào)試應(yīng)用程序用的。
-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND_FLAG:-n},address=$JAVA_DEBUG_PORT
我們調(diào)試程序更多是在IDE里面,下面的內(nèi)容來自網(wǎng)絡(luò)資料整合參考和理解:
[Debugging Java applications](Debugging Java applications - IBM Documentation) 這篇文章大概介紹了如何在JVM啟動之后調(diào)試JAVA程序,以及如何在使用JDK調(diào)試應(yīng)用程序。
若要調(diào)試 Java 進(jìn)程,可以使用 Java 調(diào)試器 (JDB) 應(yīng)用進(jìn)程或其他調(diào)試器,這些調(diào)試器通過使用 SDK 為操作系統(tǒng)提供的 Java? 平臺調(diào)試器體系結(jié)構(gòu) (JPDA) 進(jìn)行通信。
在Linux系統(tǒng)當(dāng)中進(jìn)行JAVA進(jìn)程調(diào)試可以使用下面的命令。對于我們來說這些寫法照著寫就行,不需要過分追究具體的含義。
java?-agentlib:jdwp=transport=dt_socket,server=y,address=_<port>_?<class>
調(diào)試遠(yuǎn)程服務(wù)器運(yùn)行的JAVA應(yīng)用程序,在Window中和Linux中調(diào)試方式如下:
On Windows systems:
jdb -connect com.sun.jdi.SocketAttach:hostname=<host>,port=<port>
On other systems:
jdb -attach <host>:<port>
此外Stack-Flow上還有一個寫的更棒的帖子,這篇帖子的參數(shù)和Kafka的腳本部分基本一致了。
debugging - What are Java command line options to set to allow JVM to be remotely debugged? - Stack Overflow
Before Java 5.0, use?-Xdebug
?and?-Xrunjdwp
?arguments. These options will still work in later versions, but it will run in interpreted mode instead of JIT, which will be slower.
JDK5之前的版本這里可以直接忽略。(知道了也沒啥用處)
From Java 5.0, it is better to use the?-agentlib:jdwp
?single option:
JDK5之后使用下面的命令格式:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044
Options on?-Xrunjdwp
?or?agentlib:jdwp
?arguments are :
transport=dt_socket
?: means the way used to connect to JVM (socket is a good choice, it can be used to debug a distant computer)address=8000
?: TCP/IP port exposed, to connect from the debugger,suspend=y
?: if 'y', tell the JVM to wait until debugger is attached to begin execution, otherwise (if 'n'), starts execution right away.transport=dt_socket
:表示用于連接JVM的方式(socket是一個不錯的選擇,它可以用來調(diào)試遠(yuǎn)程計算機(jī))address=8000
: TCP / IP端口公開,從調(diào)試器連接。suspend=y
:如果為“y”,則告訴 JVM 等到連接調(diào)試器后再開始執(zhí)行,否則(如果為“n”),立即開始執(zhí)行。
最后對比一下Kafka的參數(shù),豁然開朗。
-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND_FLAG:-n},address=$JAVA_DEBUG_PORT
Which java to use
如注釋所言查找java命令在哪。
if?[?-z?"$JAVA_HOME"?];?then
??JAVA="java"
else
??JAVA="$JAVA_HOME/bin/java"
fi
Memory options
內(nèi)存配置選項如下:
if?[?-z?"$KAFKA_HEAP_OPTS"?];?then
??KAFKA_HEAP_OPTS="-Xmx256M"
fi
-Xmxn:指定內(nèi)存分配池的最大大?。ㄒ宰止?jié)為單位)。此值的倍數(shù)必須大于 2MB,1024 的倍數(shù)。
這里設(shè)置最大的HEAP大小為256M。
cc_pkg
同樣是jar包依賴的查找和引入到ClassPath當(dāng)中,這里同樣不知道干啥用的,簡單理解是獲取必要依賴項即可。
for?cc_pkg?in?"api"?"transforms"?"runtime"?"file"?"mirror"?"mirror-client"?"json"?"tools"?"basic-auth-extension"
do
??for?file?in?"$base_dir"/connect/${cc_pkg}/build/libs/connect-${cc_pkg}*.jar;
??do
????if?should_include_file?"$file";?then
??????CLASSPATH="$CLASSPATH":"$file"
????fi
??done
??if?[?-d?"$base_dir/connect/${cc_pkg}/build/dependant-libs"?]?;?then
????CLASSPATH="$CLASSPATH:$base_dir/connect/${cc_pkg}/build/dependant-libs/*"
??fi
done
Exclude jars not necessary for running commands.
排除命令不需要的jar包,比如test和javadoc等。
regex="(-(test|test-sources|src|scaladoc|javadoc)\.jar|jar.asc)$"
should_include_file()?{
??if?[?"$INCLUDE_TEST_JARS"?=?true?];?then
????return?0
??fi
??file=$1
??if?[?-z?"$(echo?"$file"?|?egrep?"$regex")"?]?;?then
????return?0
??else
????return?1
??fi
}
INCLUDE_TEST_JARS
判斷是否開啟了包含測試的jar包。
if?[?-z?"$INCLUDE_TEST_JARS"?];?then
??INCLUDE_TEST_JARS=false
fi
寫在最后
不得不感嘆學(xué)無止境,知道的越多不知道的也就更多,一個腳本里面居然有這么多學(xué)問,本部分的核心毫無疑問是JVM的啟動參數(shù),其他的參數(shù)或者配置以及奇怪的腳本寫法看不懂 也沒啥關(guān)系,這里僅僅對于一些個人關(guān)注的核心部分進(jìn)行介紹,對于一些細(xì)枝末節(jié)不做過多的追究和鉆牛角尖,讀者感興趣可以對比參考資料做更多了解。