Tomcat using summary (six)

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 Lifecyclethat follows.

  1. init() // Initialize
  2. start() // start up
  3. stop() // stop
  4. destroy() // 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 -9when 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 CatalinaShutdownHookthe 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

  1. 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
  2. Secondly, we have written a self- 关闭钩子example to look at their usage
  3. 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, MessageBytesit is byte[]abstract. In this section we take a closer look!

how to use?

Let's look at a simple example MessageBytesof 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.

  1. T_NULLRepresents a null message, that message isnull
  2. T_STRIndicates that the message string type
  3. T_BYTESIt indicates that the message is a byte array type
  4. T_CHARSA 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 MessageBytesinstances.

/**
 * 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 typeto 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.

  1. Cache omitted portion
  2. In fact, here and transferred back to the ByteChunkcall 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 use java.nio.charset.CharSet.decode()instead of direct use of new String(byte[], int, int, Charset)reasons. Very often, we tend to only extract a large byte[]portion of the small inside byte[]. If you use new String(byte[], int, int, Charset), it will copy the entire byte array, seriously affect performance.

to sum up

By reading the tomcat byte[]转Stringsource 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

 

Published 370 original articles · won praise 88 · views 290 000 +

Guess you like

Origin blog.csdn.net/qq_35029061/article/details/100079235