要約を使用してTomcatの(6)
ライフサイクル
Tomcatは1つのアウトずつ積み上げアーキテクチャとコンポーネントの関数です。ほぼすべてのコンポーネントがあります生命周期组件
。何で生命周期组件
それが?ライフサイクルが人為的に組み立ての方法を実装することが可能です。そして、ライフサイクル・アプローチとそれ?私たちは、ライフサイクル・インターフェースを表示Lifecycle
次の。
init()
//初期化start()
//開始stop()
//ストップdestroy()
//破壊
これらの方法を通じて、我々は生と死のコンポーネントを制御することができます。init()
そして、start()
時にコールの開始、stop()
およびdestroy()
停止の呼び出し、彼ら同士逆操作
。私たちはTomcatののさまざまなコンポーネントを分析するとき、私たちは、ブートプロセスにもっと注意を払うが、多くの場合、プロセスを停止見落とし。
だから、Tomcatのどのメカニズムを経由コンポーネントを停止するのですか?だから、多くのコンポーネントは、それを停止するどのような順序に従っていますか?この章では、技術的なポイントに焦点を当てる必要です- 关闭钩子
。
シャットダウンフックJDK
javaプロセスの実行中に、我々は多くのリソースを開いたが、これらのリソースは、リソースの浪費を避けるために、停止javaプロセスに時間で洗浄し、解放されなければなりません。そこで、我々は、Javaプロセスが(例えば業務を行って停止する必要がkill -15
知覚するメカニズムがある場合、それをいくつかの取引を行う、)Javaプロセスを?
答えは〜です!
我々はJavaの完全なプロセスを開始する前に、Runtime.getRuntime().addShutdownHook(shutdownHook);
この方法は、登録します关闭钩子
。关闭钩子
これは、特定のある线程
人為的にコーディング、。Javaプロセスが停止する前に、呼び出しが登録されている关闭钩子
中でrun()
の方法。
私たちは、効果を確認する例を記述します。
public class ShutdownHookDemo {
public static void main(String[] args) throws Exception {
System.out.println("process is running");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("process is down. now, you can close the related resources.");
}));
Thread.sleep(Long.MAX_VALUE);
}
}
私たちは、プロセスを停止すると、私たちは次の出力を取得します。
プロセスのレンダリングを停止します
これは、Javaが来る、検証することができる关闭钩子
力です。
[警告]この例外は、我々はjavaプロセスを実行するときにある
kill -9
コマンドがとき关闭钩子
に実行されることはありません、それはノートを喜ばなければなりません!
フックの使用をオフにTomcat
Tomcatの关闭钩子
中Catalina.start()
の文のアプローチは、のは、見てみましょう。
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
// 注册关闭钩子
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
コードの主要3行は、フォーカスのために、我々は3行のコードでこれらのスティックを分離し、我々はそれ以前に非常に似て書いた例ではないでしょうか?
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
次に、私たちは分析するCatalinaShutdownHook
キーコード行をCatalina.this.stop();
、カタリナフレームワークを停止し、コードは非常にシンプルかつ簡潔です。
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
if (getServer() != null) {
Catalina.this.stop();
}
} catch (Throwable ex) {
ExceptionUtils.handleThrowable(ex);
log.error(sm.getString("catalina.shutdownHookFail"), ex);
} finally {
// If JULI is used, shut JULI down *after* the server shuts down
// so log messages aren't lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).shutdown();
}
}
}
}
概要
- まず、我々は理由を盛り付けてきた
关闭钩子
、我々は、Javaのプロセスにする必要があるため、閉じたときのクリーンアップ動作およびいくつかのリソースの解放を - 第二に、我々は自己書かれている
关闭钩子
その使用方法を見ての例を - 最後に、我々はTomcatの分析
关闭钩子
ソースコードを、私たちのチームの深化关闭钩子
理解を
MessageBytes
omcatはすぐにバイトコード変換を渡されることはありませんソケットを受けた後、パフォーマンスを向上させるが、残っているために、byte[]
方法、および使用されるときに変換します。Tomcatのの実装では、MessageBytes
それはbyte[]
抽象的。このセクションでは、よく見て!
どのように使用するには?
簡単な例で見てみましょうMessageBytes
使用する方法の。この例では、抽出するために使用されるbyte[]
内部を子byte[]
、出力を印刷します。
public static void main(String[] args) {
// 构造`MessageBytes`对象
MessageBytes mb = MessageBytes.newInstance();
// 待测试的`byte[]`对象
byte[] bytes = "abcdefg".getBytes(Charset.defaultCharset());
// 调用`setBytes()`对bytes进行标记
mb.setBytes(bytes, 2, 3);
// 转换为字符串进行控制台输出
System.out.println(mb.toString());
}
我々はそれを実行し、次の出力を参照してください、そして実際に、私たちは同じことを期待しています。
レンダリング
ソースを読みます
我々が持っている真実なしなしソースコードは、ソースコードを解析しないように。四種類の合計は、メッセージのタイプを示すために使用MessageBytesで。
T_NULL
メッセージがあることを、ヌルメッセージを表しnull
T_STR
メッセージ文字列型のことを示しますT_BYTES
これは、メッセージはバイト配列型であることを示していますT_CHARS
文字の型配列を示すメッセージ
// primary type ( whatever is set as original value )
private int type = T_NULL;
public static final int T_NULL = 0;
/** getType() is T_STR if the the object used to create the MessageBytes
was a String */
public static final int T_STR = 1;
/** getType() is T_BYTES if the the object used to create the MessageBytes
was a byte[] */
public static final int T_BYTES = 2;
/** getType() is T_CHARS if the the object used to create the MessageBytes
was a char[] */
public static final int T_CHARS = 3;
その後、我々はコンストラクタを見て、デフォルトコンストラクタは、実際には民間型で、作成するためのファクトリメソッドを提供MessageBytes
インスタンスを。
/**
* Creates a new, uninitialized MessageBytes object.
* Use static newInstance() in order to allow
* future hooks.
*/
private MessageBytes() {
}
/**
* Construct a new MessageBytes instance.
* @return the instance
*/
public static MessageBytes newInstance() {
return factory.newInstance();
}
私たちは、キーの方法を見てsetBytes()
内部のメソッド呼び出し、ByteChunk.setBytes()
メソッド、および設定しますtype字段
。
// Internal objects to represent array + offset, and specific methods
private final ByteChunk byteC=new ByteChunk();
private final CharChunk charC=new CharChunk();
/**
* Sets the content to the specified subarray of bytes.
*
* @param b the bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setBytes(byte[] b, int off, int len) {
byteC.setBytes( b, off, len );
type=T_BYTES;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
私たちは、綿密な外観を行きますByteChunk.setBytes()
。非常に単純な、それはそれを設定することで待标识的字节数组
、开始位置
、结束位置
。
private byte[] buff; // 待标记的字节数组
protected int start; // 开始位置
protected int end; // 结束位置
protected boolean isSet; // 是否已经设置过了
protected boolean hasHashCode = false; // 是否有hashCode
/**
* Sets the buffer to the specified subarray of bytes.
*
* @param b the ascii bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setBytes(byte[] b, int off, int len) {
buff = b;
start = off;
end = start + len;
isSet = true;
hasHashCode = false;
}
バイト配列が同定され、それを使用するときにされていたので?当然のことながら、それが文字列に変換し、そして私たちが考えることができる場所の変換は、オブジェクトの一般呼び出すことですされたtoString()
アプローチを、私たちは見取るMessageBytes.toString()
方法を。
@Override
public String toString() {
if( hasStrValue ) {
return strValue;
}
switch (type) {
case T_CHARS:
strValue=charC.toString();
hasStrValue=true;
return strValue;
case T_BYTES:
strValue=byteC.toString();
hasStrValue=true;
return strValue;
}
return null;
}
まず、文字列、任意の直接リターンのキャッシュがあるかどうかを決定し、これはパフォーマンスを改善する方法です。第二に、応じてtype
異なる選択し*Chunk
、その呼び出しtoString()
方法を。そこでここでは、選択しByteChunk.toString()
分析すること。
@Override
public String toString() {
if (null == buff) {
return null;
} else if (end - start == 0) {
return "";
}
return StringCache.toString(this);
}
コールStringCache.toString(this)
。StringCache
、定義、文字列のキャッシュが、私たちはメインを分析し、このセクションではMessageBytes
、それはそのキャッシュコードを無視します。高エネルギーの前に、非常に長い、この方法で見てみましょう!
public static String toString(ByteChunk bc) {
// If the cache is null, then either caching is disabled, or we're
// still training
if (bcCache == null) {
String value = bc.toStringInternal();
if (byteEnabled && (value.length() < maxStringSize)) {
// If training, everything is synced
synchronized (bcStats) {
// If the cache has been generated on a previous invocation
// while waiting for the lock, just return the toString
// value we just calculated
if (bcCache != null) {
return value;
}
// Two cases: either we just exceeded the train count, in
// which case the cache must be created, or we just update
// the count for the string
if (bcCount > trainThreshold) {
long t1 = System.currentTimeMillis();
// Sort the entries according to occurrence
TreeMap<Integer,ArrayList<ByteEntry>> tempMap =
new TreeMap<>();
for (Entry<ByteEntry,int[]> item : bcStats.entrySet()) {
ByteEntry entry = item.getKey();
int[] countA = item.getValue();
Integer count = Integer.valueOf(countA[0]);
// Add to the list for that count
ArrayList<ByteEntry> list = tempMap.get(count);
if (list == null) {
// Create list
list = new ArrayList<>();
tempMap.put(count, list);
}
list.add(entry);
}
// Allocate array of the right size
int size = bcStats.size();
if (size > cacheSize) {
size = cacheSize;
}
ByteEntry[] tempbcCache = new ByteEntry[size];
// Fill it up using an alphabetical order
// and a dumb insert sort
ByteChunk tempChunk = new ByteChunk();
int n = 0;
while (n < size) {
Object key = tempMap.lastKey();
ArrayList<ByteEntry> list = tempMap.get(key);
for (int i = 0; i < list.size() && n < size; i++) {
ByteEntry entry = list.get(i);
tempChunk.setBytes(entry.name, 0,
entry.name.length);
int insertPos = findClosest(tempChunk,
tempbcCache, n);
if (insertPos == n) {
tempbcCache[n + 1] = entry;
} else {
System.arraycopy(tempbcCache, insertPos + 1,
tempbcCache, insertPos + 2,
n - insertPos - 1);
tempbcCache[insertPos + 1] = entry;
}
n++;
}
tempMap.remove(key);
}
bcCount = 0;
bcStats.clear();
bcCache = tempbcCache;
if (log.isDebugEnabled()) {
long t2 = System.currentTimeMillis();
log.debug("ByteCache generation time: " +
(t2 - t1) + "ms");
}
} else {
bcCount++;
// Allocate new ByteEntry for the lookup
ByteEntry entry = new ByteEntry();
entry.value = value;
int[] count = bcStats.get(entry);
if (count == null) {
int end = bc.getEnd();
int start = bc.getStart();
// Create byte array and copy bytes
entry.name = new byte[bc.getLength()];
System.arraycopy(bc.getBuffer(), start, entry.name,
0, end - start);
// Set encoding
entry.charset = bc.getCharset();
// Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
bcStats.put(entry, count);
} else {
count[0] = count[0] + 1;
}
}
}
}
return value;
} else {
accessCount++;
// Find the corresponding String
String result = find(bc);
if (result == null) {
return bc.toStringInternal();
}
// Note: We don't care about safety for the stats
hitCount++;
return result;
}
}
第一印象を読んだ後には良い方法長いああです!しかし、引き算の層を剥離することにより、我々はいくつかの重要な分野を見つけ、ここでは、非クリティカル領域を除去するために見てみましょう。
- キャッシュの省略部分
- 実際には、こことに戻さ
ByteChunk
メソッドの呼び出しtoStringInternal()
public static String toString(ByteChunk bc) {
// If the cache is null, then either caching is disabled, or we're
// still training
if (bcCache == null) {
String value = bc.toStringInternal();
// 1. 省略掉的缓存部分
return value;
} else {
accessCount++;
// Find the corresponding String
String result = find(bc);
if (result == null) {
// 2. 这儿其实又调回了`ByteChunk`,调用的方法为`toStringInternal()`
return bc.toStringInternal();
}
// Note: We don't care about safety for the stats
hitCount++;
return result;
}
}
我々は見てByteChunk.toStringInternal()
の方法、鍵がここにある場所を推測します!
public String toStringInternal() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
// new String(byte[], int, int, Charset) takes a defensive copy of the
// entire byte array. This is expensive if only a small subset of the
// bytes will be used. The code below is from Apache Harmony.
CharBuffer cb = charset.decode(ByteBuffer.wrap(buff, start, end - start));
return new String(cb.array(), cb.arrayOffset(), cb.length());
}
案の定!それはここにある偏移量
と待提取长度
行います编码提取转换
。
特別な注意が必要な
toStringInternal()
内部のコメントに、コメントは、使用について説明したが、java.nio.charset.CharSet.decode()
代わりに直接使用のnew String(byte[], int, int, Charset)
理由から。非常に多くの場合、我々は唯一の大規模抽出する傾向にあるbyte[]
小さな内側の部分をbyte[]
。あなたが使用している場合new String(byte[], int, int, Charset)
、それは真剣にパフォーマンスに影響を与え、全体のバイト配列をコピーします。
概要
Tomcatの読み取ることによりbyte[]转String
、ソースコードを、私たちが見るTomcatの開発チームは、Webサーバのパフォーマンスを改善するために、せっかくに言うことができます。変換アイデアはであること、非常に簡単です打标记
+ 延时提取
実装に按需使用
。することで编码提取转换
、時間の特別な変換ロジックの使用