分类目录归档:java

jstack不可用的原因

上午遇到的jstack没有输出的问题,印象里在阿里前几年也遇到过,不记得细节了;晚上查了一下,是jdk1.6的某些特定版本存在的bug,见这篇blog,简单的说就是本来hsperfdata_$user 这个目录一直约定是在系统的临时目录下的,对linux来说是”/tmp”

用 strace 来看一下正常版本启动jvm,hsperfdata_$user目录不受java.io.tmpdir变量影响

$ strace -ff -e trace=file  java -Djava.io.tmpdir=/data/test -version 2>&1 | perl -ne 's/^[^"]+"(([^\\"]|\\[\\"nt])*)".*/$1/ && print'  | grep hsperfdata

/tmp/hsperfdata_hongjiang
/tmp/hsperfdata_hongjiang/17019

而在 jdk1.6.0_21到24引入了bug,使用了jvm的环境变量-Djava.io.tmpdir做为了hsperfdata_$user的路径,正好一些应用比如tomcat是对这个变量设置了自己的路径的,比如”/server/tomcat/temp”,存在bug的这些版本把hsperfdata_$user目录也放到tomcat环境变量指定的目录下了,结果 jps, jstack, 之类工具都不能找到目标了。根据bug报告里给出的一种变通方式,在运行jstack时设置-J-Djava.io.tmpdir=$CATALINA_HOME/temp 也并未能工作。对于这个问题最佳的处理方式还是升级jdk版本。

补充:

虽然对jstack设置变量并不工作,但发现对jinfo设置该变量是有效的,比如:jinfo -J-Djava.io.tmpdir=/server/tomcat/temp -sysprops $pid 在jdk1.6.0_24下是可以工作的。

BoneCP的线程阻塞问题

某个业务采用BoneCP连接池遇到的情况,200个dubbo线程都阻塞在com.jolbox.bonecp.BoneCPDataSource.maybeInit方法上,这里wait的是同一个锁:

"DubboServerHandler-thread-203" daemon prio=10 tid=0x00007fc2400e7800 nid=0x658c waiting on condition [0x00007fc233dbb000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000c071f518> (a java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireShared(AbstractQueuedSynchronizer.java:964)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireShared(AbstractQueuedSynchronizer.java:1282)
at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.lock(ReentrantReadWriteLock.java:731)
at com.jolbox.bonecp.BoneCPDataSource.maybeInit(BoneCPDataSource.java:133)
at com.jolbox.bonecp.BoneCPDataSource.getConnection(BoneCPDataSource.java:112)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
at org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:116)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:333)
at com.sun.proxy.$Proxy11.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:189)
at org.apache.ibatis.binding.MapperMethod.executeForList(MapperMethod.java:100)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:70)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:38)

从BoneCP的代码里看不出直接问题,是一个ReentrantReadWriteLock去获取锁的逻辑:

private void maybeInit() throws SQLException {
    this.rwl.readLock().lock();  // ------->  133行,所有线程都阻塞在这里
    if (this.pool == null){
        this.rwl.readLock().unlock();
        this.rwl.writeLock().lock();
        if (this.pool == null){ //read might have passed, write might not
            try {
                if (this.getDriverClass() != null){
                    loadClass(this.getDriverClass());
                }
            }
            catch (ClassNotFoundException e) {
                throw new SQLException(PoolUtil.stringifyException(e));
            }


            logger.debug(this.toString());

            this.pool = new BoneCP(this);
        }

        this.rwl.writeLock().unlock(); // Unlock write
    } else {
        this.rwl.readLock().unlock(); // Unlock read
    }
}

按说通常如果是死锁的话,应该是某个线程A先获取到锁X,然后再去获取另一个锁Y时发被线程B占用,且线程B又要获取锁X,但这里的现象却是都在等同一把锁,并且一直处于这种状态导致服务不可用。jstack -F看看是否能检测到死锁,输出也是No deadlocks found. (也可能是ReentrantLock的读锁没有ownership的原因,jvm无法跟踪并检测出来,参考这篇这个bug里Doug Lea的回复),怀疑也可能跟jdk和os的版本有关,可能是该版本的bug(或其他我不了解的)。

maven wrapper script

这个脚本用于简化mvn的操作,在mac/bash, linux/bash, cygwin/bash下测试过

#!/bin/bash - 

TOP_PID=$$
PROG_NAME=$0
ACTION=$1

function get_children() {
  ps -ef | grep $$ | grep -v grep | awk '$3=='"$$"'{print $2}' | xargs
}

trap "kill $(get_children) >/dev/null 2>&1; exit 1" SIGINT SIGTERM

function usage() {
  echo "Usage: $PROG_NAME {verify|compile|run|debug|package|eclipse|get-src|dep-tree}"
  exit 1;
}

function get_mvn_cmd() {
  if [[ "$OSTYPE" == *cygwin* ]];then
    ppid=$( ps -ef -p $$ | awk 'NR==2{print $3}' )
    user_shell=$( ps -p $ppid | awk 'NR==2{print $8}' )
    #has some trouble with cygwin, while Ctrl-c cannot terminal
    set -m
  else
    ppid=$( ps -oppid= $$ )
    user_shell=$( ps -ocomm= -p $ppid )
  fi

  mvn=$( $user_shell -ic "alias mvn" 2>/dev/null | cut -d'=' -f2 | sed "s/'//g" )

  if [ -z "$mvn" ];then
    $user_shell -ic "which mvn" >/dev/null
    if [ $? -eq 0 ];then
      mvn=$( $user_shell -ic "which mvn" | head -1 )
    fi
  fi

  if [ -z "$mvn" ]; then 
    echo "mvn command not found" >&2
    kill -s TERM $TOP_PID
  else
    echo $mvn
  fi
}

MVN=$( get_mvn_cmd )

function mvn_verify() {
  $MVN verify $@
}

function mvn_compile() {
  $MVN clean compile $@
}

function mvn_run() {
  $MVN scala:run $@
}

function mvn_debug() {
  $MVN scala:run -Dlauncher=debug $@
}

function classpath_check() {
  if [ $# -eq 0 ];then
    echo "please enter classpath dir"
    exit 1
  fi

  if [ ! -d "$1" ]; then 
    echo "not a directory"
    exit 2
  fi

  tmpfile="/tmp/.cp$(date +%s)"
  tmphash="/tmp/.hash$(date +%s)"
  verbose="/tmp/cp-verbose.log"

  declare -a files=(`find "$1" -name "*.jar"`)
  for ((i=0; i < ${#files[@]}; i++)); do
    jarName=`basename ${files[$i]}`
    list=`unzip -l ${files[$i]} | awk -v fn=$jarName '/\.class$/{print $NF,fn}'`
    size=`echo "$list" | wc -l`
    echo $jarName $size >> $tmphash
    echo "$list" 
  done | sort | awk 'NF{
      a[$1]++;m[$1]=m[$1]","$2}END{for(i in a) if(a[i] > 1) print i,substr(m[i],2)
  }' > $tmpfile

  awk '{print $2}' $tmpfile | 
  awk -F',' '{i=1;for(;i<=NF;i++) for(j=i+1;j<=NF;j++) print $i,$j}' | 
  sort | uniq -c | sort -nrk1 | while read line; do
    dup=${line%% *} 
    jars=${line#* }
    jar1=${jars% *}
    jar2=${jars#* }
    len_jar1=`grep -F "$jar1" $tmphash | grep ^"$jar1" | awk '{print $2}'`
    len_jar2=`grep -F "$jar2" $tmphash | grep ^"$jar2" | awk '{print $2}'`
    len=$(($len_jar1 > $len_jar2 ? $len_jar1 : $len_jar2))
    per=$(echo "scale=2; $dup/$len" | bc -l)
    echo ${per/./} $dup $jar1 $jar2 
  done | sort -nr -k1 -k2 | 
  awk 'NR==1{print "Similarity DuplicateClasses File1 File2"}{print "%"$0}'| column -t  

  sort $tmpfile | awk '{print $1,"\n\t\t",$2}' > $verbose
  echo "See $verbose for more details."

  rm -f $tmpfile
  rm -f $tmphash
}

function mvn_package() {
  $MVN clean package -Dmaven.javadoc.skip -Dmaven.test.skip $@
  if [ $? -eq 0 ];then
    echo ""
    classpath_check ./target/lib
  fi
}

function mvn_src() {
  $MVN dependency:sources
}

function mvn_dep_tree() {
  $MVN dependency:tree -Dverbose
}

function mvn_eclipse() {
  $MVN eclipse:eclipse
}

case "$ACTION" in
  verify)
    shift && mvn_verify $@
  ;;
  compile)
    shift && mvn_compile $@
  ;;
  run)
    shift && mvn_run $@
  ;;
  debug)
    shift && mvn_debug $@
  ;;
  package)
    shift && mvn_package $@
  ;;
  get-src)
    mvn_src
  ;;
  dep-tree)
    mvn_dep_tree
  ;;
  eclipse)
    mvn_eclipse
  ;;
  *)
    usage
  ;;
esac

servlet模型(3.1之前)对InputStream的处理是阻塞模式

同事聊起这个话题,当请求建立时是否tomcat就为每个连接分配线程了? 是否只要发起足够多的连接不必发送任何数据就可以DDoS了?对于DDoS这个话题不展开,这里仅仅说一下连接过来时,服务器端是否就一定分配了线程。这取决于tomcat配置的connector模式,只讨论一下bio和nio的情况。

以bio模式启动tomcat,然后建立3个连接(不发送任何数据):

$ nc localhost 8080 &
$ nc localhost 8080 &
$ nc localhost 8080 &

这时我们来看tomcat的bio执行线程是与连接数一对一的:

$ ./appctrl.sh jstack | grep http-bio-8080-exec
"http-bio-8080-exec-3" #24 daemon prio=5 os_prio=31 tid=0x00007fdd7a840800 nid=0x6607 runnable [0x000000011d78b000]
"http-bio-8080-exec-2" #23 daemon prio=5 os_prio=31 tid=0x00007fdd7a83f800 nid=0x6507 runnable [0x000000011d618000]
"http-bio-8080-exec-1" #22 daemon prio=5 os_prio=31 tid=0x00007fdd7a800000 nid=0x4107 runnable [0x000000011addd000]

而在nio模式下,请求建立时,并不会有执行线程,只有接收到数据发时,才会有线程分配:

$ nc localhost 8080
POST /test/post?name=foo HTTP/1.1
Host: localhost:8080
Context-Length: 10000 

上面在nc建立连接后,发送了一段http header(还未发送body,请求保持中),这时才会对这些请求数据分配线程阻塞执行:

$ ./appctrl.sh jstack | grep http-nio-8080-exec
"http-nio-8080-exec-3" #26 daemon prio=5 os_prio=31 tid=0x00007ffefb001800 nid=0x6007 waiting on condition [0x0000000123af7000]
"http-nio-8080-exec-2" #25 daemon prio=5 os_prio=31 tid=0x00007ffefa029000 nid=0x5d07 waiting on condition [0x000000012351c000]
"http-nio-8080-exec-1" #24 daemon prio=5 os_prio=31 tid=0x00007ffef900a000 nid=0x680b waiting on condition [0x000000012328f000] 

上面的测试是在tomcat7下测试的,nio在处理http request的时候是非阻塞的,但读取数据的时候是模拟阻塞的,因为servlet3.1之前对input的处理就是一种阻塞模式,参考以前的一张截图:

jdk的HttpURLConnection提供了日志开关

下午运行一个junit程序,时不时启动时就“卡住”,没有输出任何日志,通过jstack发现是spring初始化时调用了sun.net.www.protocol.http.HttpURLConnection访问网络时阻塞住了。按说依赖的spring的jar里都应该包含了这些dtd/xsd之类的文件,怎么仍会访问远程网络呢?

jdk自带的HttpURLConnection提供了日志(JUL)开关,可以查看有哪些URL请求,在运行时指定

-Djava.util.logging.config.file=/tmp/logging.properties

不指定的话,默认从 jre/lib/logging.properties 加载。在配置文件里:

# 增加一条针对HttpURLConnection的配置
sun.net.www.protocol.http.HttpURLConnection.level = ALL

# 并修改终端的输出级别
java.util.logging.ConsoleHandler.level = ALL

启动后看到如下日志:

FINEST: ProxySelector Request for http://www.springframework.org/dtd/spring-beans.dtd
Jul 25, 2015 4:49:45 PM sun.net.www.protocol.http.HttpURLConnection plainConnect0

FINEST: Proxy used: DIRECT
Jul 25, 2015 4:49:45 PM sun.net.www.protocol.http.HttpURLConnection writeRequests

FINE: sun.net.www.MessageHeader@531be3c55 pairs: 
{GET /dtd/spring-beans.dtd HTTP/1.1: null}
{User-Agent: Java/1.8.0_20}
{Host: www.springframework.org}
{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}
{Connection: keep-alive}
Jul 25, 2015 4:49:45 PM sun.net.www.protocol.http.HttpURLConnection getInputStream0

FINE: sun.net.www.MessageHeader@52af6cff16 pairs: {null: HTTP/1.1 200 OK}...

日志里看到确实是去访问了spring网站,根据url信息找到包含spring-beans.dtd内容的xml配置;这个工程是一个很老的工程,里的写法是:

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

要防止springframework.org或者GFW引发的不稳定,采用新的写法来避免:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.1.xsd ">

fastjson与awt

晚上在repl下模拟一个json的解析时发现,当调用fastjson时会在我的dock上弹出一个Java应用的icon,如下图:

这通常是程序里引用awt/swing之类的情况下才会发生,我很奇怪fastjson怎么会引起,对repl添加启动参数-J-verbose:class看看到底是否有加载awt先关的类:

看来温少考虑到了这种类型的解析场景,在代码里有相关的引用。

觉得心烦的话,启动repl时加一个java.awt.headless=true参数吧。

再谈随机数引起的阻塞问题

Java的随机数实现有很多坑,记录一下这次使用jdk1.8里新增的加强版随机数实现SecureRandom.getInstanceStrong() 遇到的问题。

之前在维护ali-tomcat的时候曾发现过jvm随机数算法选用不当导致tomcat的SessionID生成非常慢的情况,可以参考JVM上的随机数与熵池策略Docker中apache-tomcat启动慢的问题 这两篇文章。不过当时没有太追究,以为使用了-Djava.security.egd=file:/dev/./urandom就可以避免了,在这次项目里再次遇到随机数导致所有线程阻塞之后发现这块还挺多规则。

本次项目中使用的是jdk1.8,启动参数里设置了

-Djava.security.egd=file:/dev/./urandom

使用的随机数方式是Java8新增的:

SecureRandom.getInstanceStrong();

碰到故障时,线程阻塞在

"DubboServerHandler-xxx:20880-thread-1789" #28440 daemon prio=5 os_prio=0 tid=0x0000000008ffd000 nid=0x5712 runnable [0x000000004cbd7000]
java.lang.Thread.State: RUNNABLE
    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:246)
    at sun.security.provider.NativePRNG$RandomIO.readFully(NativePRNG.java:410)
    at sun.security.provider.NativePRNG$RandomIO.implGenerateSeed(NativePRNG.java:427)
    - locked <0x00000000c03a3c90> (a java.lang.Object)
    at sun.security.provider.NativePRNG$RandomIO.access$500(NativePRNG.java:329)
    at sun.security.provider.NativePRNG$Blocking.engineGenerateSeed(NativePRNG.java:272)
    at java.security.SecureRandom.generateSeed(SecureRandom.java:522)

因为这个地方有加锁,locked <0x00000000c03a3c90>,所以其它线程调用到这里时会等待这个lock:

"DubboServerHandler-xxx:20880-thread-1790" #28441 daemon prio=5 os_prio=0 tid=0x0000000008fff000 nid=0x5713 waiting for monitor entry [0x000000004ccd8000]
java.lang.Thread.State: BLOCKED (on object monitor)
    at sun.security.provider.NativePRNG$RandomIO.implGenerateSeed(NativePRNG.java:424)
    - waiting to lock <0x00000000c03a3c90> (a java.lang.Object)
    at sun.security.provider.NativePRNG$RandomIO.access$500(NativePRNG.java:329)
    at sun.security.provider.NativePRNG$Blocking.engineGenerateSeed(NativePRNG.java:272)
    at java.security.SecureRandom.generateSeed(SecureRandom.java:522)

去查 NativePRNG$Blocking代码,看到它的文档描述:

A NativePRNG-like class that uses /dev/random for both seed and random material. Note that it does not respect the egd properties, since we have no way of knowing what those qualities are.

奇怪怎么-Djava.security.egd=file:/dev/./urandom参数没起作用,仍使用/dev/random作为随机数的熵池,时间久或调用频繁的话熵池很容易不够用而导致阻塞;于是看了一下 SecureRandom.getInstanceStrong()的文档:

Returns a SecureRandom object that was selected by using the algorithms/providers specified in the securerandom.strongAlgorithms Security property.

原来有自己的算法,在 jre/lib/security/java.security 文件里,默认定义为:

securerandom.strongAlgorithms=NativePRNGBlocking:SUN

如果修改算法值为NativePRNGNonBlocking:SUN的话,会采用NativePRNG$NonBlocking里的逻辑,用/dev/urandom作为熵池,不会遇到阻塞问题。但这个文件是jdk系统文件,修改它或重新指定一个路径都有些麻烦,最好能通过系统环境变量来设置,可这个变量不像securerandom.source属性可以通过系统环境变量-Djava.security.egd=xxx来配置,找半天就是没有对应的系统环境变量。只好修改代码,不采用SecureRandom.getInstanceStrong这个新方法,改成了SecureRandom.getInstance("NativePRNGNonBlocking")

对于SecureRandom的两种算法实现:SHA1PRNGNativePRNGsecurerandom.source 变量的关系,找到一篇解释的很清楚的文章:Using the SecureRandom Class

On Linux:

1) when this value is “file:/dev/urandom” then the NativePRNG algorithm is registered by the Sun crypto provider as the default implementation; the NativePRNG algorithm then reads from /dev/urandom for nextBytes but /dev/random for generateSeed

2) when this value is “file:/dev/random” then the NativePRNG algorithm is not registered by the Sun crypto provider, but the SHA1PRNG system uses a NativeSeedGenerator which reads from /dev/random.

3) when this value is anything else then the SHA1PRNG is used with a URLSeedGenerator that reads from that source.

4) when the value is undefined, then SHA1PRNG is used with ThreadedSeedGenerator

5) when the code explicitly asks for “SHA1PRNG” and the value is either “file:/dev/urandom” or “file:/dev/random” then (2) also occurs

6) when the code explicitly asks for “SHA1PRNG” and the value is some other “file:” url, then (3) occurs

7) when the code explicitly asks for “SHA1PRNG” and the value is undefined then (4) occurs

至于SHA1PRNG算法里,为何用urandom时,不能直接设置为file:/dev/urandom而要用变通的方式设置为file:///dev/urandom或者 file:/dev/./urandom,参考这里

In SHA1PRNG, there is a SeedGenerator which does various things depending on the configuration.

  1. If java.security.egd or securerandom.source point to “file:/dev/random” or “file:/dev/urandom”, we will use NativeSeedGenerator, which calls super() which calls SeedGenerator.URLSeedGenerator(/dev/random). (A nested class within SeedGenerator.) The only things that changed in this bug was that urandom will also trigger use of this code path.

  2. If those properties point to another URL that exists, we’ll initialize SeedGenerator.URLSeedGenerator(url). This is why “file:///dev/urandom”, “file:/./dev/random”, etc. will work.

maven调试web应用

mvn tomcat7:run运行web应用的一个小脚本,方便debug:

$ cat mvn-tomcat
#!/bin/bash

suspend="n"
if [ "$1" != "" ]; then
  lower=`echo $1 | tr '[:upper:]' '[:lower:]'`
  if [ "$lower" == "y" ] || [ "$lower" == "n" ]; then
    suspend=$lower
  else
    echo "param error" && exit -1;
  fi
fi

port=8080
if [ "$2" != "" ]; then
  re='^[0-9]+$'
  if ! [[ "$2" =~ $re ]] ; then
     echo "port: error, not a number" && exit -2;
  else
    port=$2
  fi
fi

export MAVEN_OPTS=-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend="$suspend"
mvn tomcat7:run -Dmaven.tomcat.port="$port"

把脚本放到放到PATH路径下(比如~/bin目录下)。使用方式:

$ mvn-tomcat # 默认8080端口

$ mvn-tomcat y # 断点suspend

$ mvn-tomcat n 7001 # 指定tomcat用7001端口

tomcat nio模式下sendfile引起的NPE

这个bug困扰我们很长一段时间,最初是在生产环境发现的,为了确保项目发布,紧急情况下让应用切换成了BIO。后来没能重现,大家没足够重视,一直没有去跟这个问题,直到最近再次发现这个问题,发现是NIO模式默认对静态资源启用了sendfile以提升性能,但这里存在bug所致。官方已经在7051后续版本修复了这个问题,最好升级到最新版本。或者在server.xml的Connector节点里增加: useSendfile=”false” 来避免。

下面是相关的异常信息,如果你的tomcat是7051之前的版本,采用NIO并且没有显式的关闭sendfile,应用里有静态资源,访问静态资源时tomcat日志里出现了下面的异常(如果前边有nginx或apache返回502),很可能是同一问题:

 java.lang.NullPointerException
     at org.apache.catalina.connector.Request.notifyAttributeAssigned(Request.java:1565)
     at org.apache.catalina.connector.Request.setAttribute(Request.java:1556)
     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:178)
     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:410)
     at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1043)
     at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721)
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
     at java.lang.Thread.run(Thread.java:744)

java.lang.NullPointerException
     at org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:210)
     at org.apache.coyote.http11.InternalNioOutputBuffer.commit(InternalNioOutputBuffer.java:202)
     at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:781)
     at org.apache.coyote.Response.action(Response.java:172)
     at org.apache.coyote.http11.AbstractOutputBuffer.endRequest(AbstractOutputBuffer.java:302)
     at org.apache.coyote.http11.InternalNioOutputBuffer.endRequest(InternalNioOutputBuffer.java:120)
     at org.apache.coyote.http11.AbstractHttp11Processor.endRequest(AbstractHttp11Processor.java:1743)
     at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1087)
     at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721)
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
     at java.lang.Thread.run(Thread.java:744)

 java.lang.NullPointerException
     at org.apache.tomcat.util.buf.MessageBytes.toBytes(MessageBytes.java:244)
     at org.apache.catalina.connector.CoyoteAdapter.parsePathParameters(CoyoteAdapter.java:807)
     at org.apache.catalina.connector.CoyoteAdapter.postParseRequest(CoyoteAdapter.java:579)
     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:405)
     at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1043)
     at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721)
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
     at java.lang.Thread.run(Thread.java:744)

java.lang.NullPointerException
     at org.apache.coyote.http11.InternalNioOutputBuffer.flushBuffer(InternalNioOutputBuffer.java:233)
     at org.apache.coyote.http11.InternalNioOutputBuffer.endRequest(InternalNioOutputBuffer.java:121)
     at org.apache.coyote.http11.AbstractHttp11Processor.endRequest(AbstractHttp11Processor.java:1743)
     at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1087)
     at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721)
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
     at java.lang.Thread.run(Thread.java:744)         

tomcat-connector的微调(5): keep-alive相关的参数

tomcat默认是开启keep-alive的,有3个相关的参数可以配置:

1) keepAliveTimeout

表示在复用一个连接时,两次请求之间的最大间隔时间;超过这个间隔服务器会主动关闭连接。默认值同connectionTimeout参数,即20秒。不做限制的话可以设置为-1.

2) maxKeepAliveRequests

表示一个连接最多可复用多少次请求,默认是100。不做限制可以设置为-1. 注意如果tomcat是直接对外服务的话,keep-alive特性可能被一些DoS攻击利用,比如以很慢的方式发生数据,可以长时间持有这个连接;从而可能被恶意请求耗掉大量连接拒绝服务。tomcat直接对外的话这个值不宜设置的太大。

3) disableKeepAlivePercentage

注意,这个参数是BIO特有的,默认情况BIO在线程池里的线程使用率超过75%时会取消keep-alive,如果不取消的话可以设置为100.