Tomcat using summary (six)
Life cycle
Tomcat is a function of the architecture and components piling up one by one out. Almost every component is 生命周期组件
. What is 生命周期组件
it? Is life-cycle can be artificially implemented method of assembly. Then the life-cycle approach and which it? We view the life cycle interface Lifecycle
that follows.
init()
// Initializestart()
// start upstop()
// stopdestroy()
// Destruction
Through these methods, we can control life and death components. init()
And start()
when the start of the call, stop()
and the destroy()
call to stop, they each other 逆操作
. When we analyze the various components of the tomcat, we pay more attention to the boot process, but often overlooked stop the process.
So tomcat it is to stop the component through which mechanism it? So many components is in accordance with what order to stop it? This chapter is the need to focus on technical points - 关闭钩子
.
The shutdown hook jdk
During the java process running, we opened up a lot of resources, but these resources must be cleaned and released in time to stop java process, to avoid waste of resources. So, we have to stop java process carried out operations (for example kill -15
), java process if there is a mechanism to perceive, then do some deal with it?
The answer is ~!
Before we can start the process of java complete, the Runtime.getRuntime().addShutdownHook(shutdownHook);
method to register a 关闭钩子
. 关闭钩子
It is a specific 线程
, artificially Coding. java process before stopping, calls have been registered 关闭钩子
in the run()
method.
We write an example to see the effect.
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);
}
}
When we stop the process, we get the following output.
Stop the process renderings
It can be verified, java comes 关闭钩子
is in force.
[Warning] exception to this is when we perform java process
kill -9
when the command关闭钩子
will not be executed, it must please note!
tomcat turn off the use of hook
the tomcat 关闭钩子
in Catalina.start()
a statement approach, let's take a look.
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();
}
}
The key three lines of code, in order to focus, we separate these stick out on three lines of code, and is not an example we wrote earlier very similar?
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
Next, we analyze CatalinaShutdownHook
the key code line Catalina.this.stop();
, stop Catalina framework, the code is very simple and concise.
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();
}
}
}
}
to sum up
- First, we have dished out why
关闭钩子
, because we need to be in the process of java closed when the clean-up operation and the release of some resources - Secondly, we have written a self-
关闭钩子
example to look at their usage - Finally, we analyze the tomcat
关闭钩子
source code, deepened our team关闭钩子
understanding
MessageBytes
omcat To improve performance, after receiving the socket will not be passed immediately byte code conversion, but remains byte[]
way, and then convert when used. In the implementation of tomcat, MessageBytes
it is byte[]
abstract. In this section we take a closer look!
how to use?
Let's look at a simple example MessageBytes
of how to use. This example is used to extract byte[]
an inside 子byte[]
, then print output.
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());
}
We run it, see the following output, and indeed we expect the same.
Renderings
Source Reading
No source code without truth, which we have to analyze the source code. In MessageBytes which a total of four types, used to indicate the type of message.
T_NULL
Represents a null message, that message isnull
T_STR
Indicates that the message string typeT_BYTES
It indicates that the message is a byte array typeT_CHARS
A message indicating the type array of characters
// 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;
Then we look at the constructor, the default constructor is actually the private type, and provides a factory method for creating MessageBytes
instances.
/**
* 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();
}
We then look at key method setBytes()
, the method calls inside the ByteChunk.setBytes()
method, and set up 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;
}
We go in-depth look ByteChunk.setBytes()
. Very simple, it is to set it 待标识的字节数组
, 开始位置
, 结束位置
.
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;
}
Since the byte array had been identified, and that when to use it? Naturally it is converted to a String, and the conversion of where we can think of is to call the object's general toString()
approach, we take a look at MessageBytes.toString()
methods.
@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;
}
First, determine whether there is a cache of string, any direct return, and this is a way to improve performance. Secondly, according type
to choose different *Chunk
, then call its toString()
methods. So here we choose ByteChunk.toString()
to analyze.
@Override
public String toString() {
if (null == buff) {
return null;
} else if (end - start == 0) {
return "";
}
return StringCache.toString(this);
}
Call StringCache.toString(this)
. StringCache
, By definition, the string cache, but this section we analyze the main MessageBytes
, it will ignore its cache code. Let's look at this method, which is very long, in front of high energy!
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;
}
}
After reading the first impression is a good method long ah! But by peeling layers of subtraction, we found few key areas, here we take a look at to remove non-critical areas.
- Cache omitted portion
- In fact, here and transferred back to the
ByteChunk
call of the methodtoStringInternal()
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;
}
}
We look at the ByteChunk.toStringInternal()
method, guess where is the key here!
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());
}
Sure enough! It is in here 偏移量
and 待提取长度
perform 编码提取转换
.
Require special attention
toStringInternal()
to internal comments, the comment has been given of the usejava.nio.charset.CharSet.decode()
instead of direct use ofnew String(byte[], int, int, Charset)
reasons. Very often, we tend to only extract a largebyte[]
portion of the small insidebyte[]
. If you usenew String(byte[], int, int, Charset)
, it will copy the entire byte array, seriously affect performance.
to sum up
By reading the tomcat byte[]转String
source code, we see tomcat development team can be said to great pains to improve the performance of web servers. Conversion idea is very simple, that is by 打标记
+ 延时提取
to implementations 按需使用
. By 编码提取转换
the time the use of a special conversion logic