java线程池参数说明

java线程池

ThreadPoolExecutor
来窥探线程池核心类的构造函数,我们需要理解每一个参数的作用,才能理解线程池的工作原理。

1
2
3
4
5
6
7
8
9
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
......
}
  1. corePoolSize:保留在池中的线程数,即使它们空闲,除非设置了 allowCoreThreadTimeOut,不然不会关闭。
  2. maximumPoolSize:队列满后池中允许的最大线程数。
  3. keepAliveTime、TimeUnit:如果线程数大于核心数,多余的空闲线程的保持的最长时间会被销毁。unit 是 keepAliveTime 参数的时间单位。当设置 allowCoreThreadTimeOut(true) 时,线程池中 corePoolSize 范围内的线程空闲时间达到 keepAliveTime 也将回收。
  4. workQueue:当线程数达到 corePoolSize 后,新增的任务就放到工作队列 workQueue 里,而线程池中的线程则努力地从 workQueue 里拉活来干,也就是调用 poll 方法来获取任务。
  5. ThreadFactory:创建线程的工厂,比如设置是否是后台线程、线程名等。
  6. RejectedExecutionHandler:拒绝策略,处理程序因为达到了线程界限和队列容量执行拒绝策略。也可以自定义拒绝策略,只要实现 RejectedExecutionHandler 即可。默认的拒绝策略:AbortPolicy 拒绝任务并抛出 RejectedExecutionException 异常;CallerRunsPolicy 提交该任务的线程执行;
  • 来分析下每个参数之间的关系:
    提交新任务的时候,如果线程池数 < corePoolSize,则创建新的线程池执行任务,当线程数 = corePoolSize 时,新的任务就会被放到工作队列 workQueue 中,线程池中的线程尽量从队列里取任务来执行。
    如果任务很多,workQueue 满了,且 当前线程数 < maximumPoolSize 时则临时创建线程执行任务,如果总线程数量超过 maximumPoolSize,则不再创建线程,而是执行拒绝策略。DiscardPolicy 什么都不做直接丢弃任务;DiscardOldestPolicy 丢弃最旧的未处理程序;

tomcat线程池

定制版的 ThreadPoolExecutor,继承了 java.util.concurrent.ThreadPoolExecutor。 对于线程池有两个很关键的参数:

线程个数。
队列长度。

Tomcat 必然需要限定想着两个参数不然在高并发场景下可能导致 CPU 和内存有资源耗尽的风险。继承了 与 java.util.concurrent.ThreadPoolExecutor 相同,但实现的效率更高。
其构造方法如下,跟 Java 官方的如出一辙

1
2
3
4
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
prestartAllCoreThreads();
}

在 Tomcat 中控制线程池的组件是 StandardThreadExecutor , 也是实现了生命周期接口,下面是启动线程池的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected void startInternal() throws LifecycleException {
// 自定义任务队列
taskqueue = new TaskQueue(maxQueueSize);
// 自定义线程工厂
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
// 创建定制版线程池
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
executor.setThreadRenewalDelay(threadRenewalDelay);
if (prestartminSpareThreads) {
executor.prestartAllCoreThreads();
}
taskqueue.setParent(executor);
// 观察者模式,发布启动事件
setState(LifecycleState.STARTING);
}

其中的关键点在于:

Tomcat 有自己的定制版任务队列和线程工厂,并且可以限制任务队列的长度,它的最大长度是 maxQueueSize。
Tomcat 对线程数也有限制,设置了核心线程数(minSpareThreads)和最大线程池数(maxThreads)。

除此之外, Tomcat 在官方原有基础上重新定义了自己的线程池处理流程,原生的处理流程上文已经说过。

前 corePoolSize 个任务时,来一个任务就创建一个新线程。
还有任务提交,直接放到队列,队列满了,但是没有达到最大线程池数则创建临时线程救火。
线程总线数达到 maximumPoolSize ,直接执行拒绝策略。

Tomcat 线程池扩展了原生的 ThreadPoolExecutor,通过重写 execute 方法实现了自己的任务处理逻辑:

前 corePoolSize 个任务时,来一个任务就创建一个新线程。
还有任务提交,直接放到队列,队列满了,但是没有达到最大线程池数则创建临时线程救火。
线程总线数达到 maximumPoolSize ,继续尝试把任务放到队列中。如果队列也满了,插入任务失败,才执行拒绝策略。

最大的差别在于 Tomcat 在线程总数达到最大数时,不是立即执行拒绝策略,而是再尝试向任务队列添加任务,添加失败后再执行拒绝策略。
代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void execute(Runnable command, long timeout, TimeUnit unit) {
// 记录提交任务数 +1
submittedCount.incrementAndGet();
try {
// 调用 java 原生线程池来执行任务,当原生抛出拒绝策略
super.execute(command);
} catch (RejectedExecutionException rx) {
//总线程数达到 maximumPoolSize,Java 原生会执行拒绝策略
if (super.getQueue() instanceof TaskQueue) {
final TaskQueue queue = (TaskQueue)super.getQueue();
try {
// 尝试把任务放入队列中
if (!queue.force(command, timeout, unit)) {
submittedCount.decrementAndGet();
// 队列还是满的,插入失败则执行拒绝策略
throw new RejectedExecutionException("Queue capacity is full.");
}
} catch (InterruptedException x) {
submittedCount.decrementAndGet();
throw new RejectedExecutionException(x);
}
} else {
// 提交任务书 -1
submittedCount.decrementAndGet();
throw rx;
}

}
}

Tomcat 线程池是用 submittedCount 来维护已经提交到了线程池,这跟 Tomcat 的定制版的任务队列有关。Tomcat 的任务队列 TaskQueue 扩展了 Java 中的 LinkedBlockingQueue,我们知道 LinkedBlockingQueue 默认情况下长度是没有限制的,除非给它一个 capacity。因此 Tomcat 给了它一个 capacity,TaskQueue 的构造函数中有个整型的参数 capacity,TaskQueue 将 capacity 传给父类 LinkedBlockingQueue 的构造函数,防止无限添加任务导致内存溢出。而且默认是无限制,就会导致当前线程数达到核心线程数之后,再来任务的话线程池会把任务添加到任务队列,并且总是会成功,这样永远不会有机会创建新线程了。
为了解决这个问题,TaskQueue 重写了 LinkedBlockingQueue 的 offer 方法,在合适的时机返回 false,返回 false 表示任务添加失败,这时线程池会创建新的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class TaskQueue extends LinkedBlockingQueue<Runnable> {

...
@Override
// 线程池调用任务队列的方法时,当前线程数肯定已经大于核心线程数了
public boolean offer(Runnable o) {

// 如果线程数已经到了最大值,不能创建新线程了,只能把任务添加到任务队列。
if (parent.getPoolSize() == parent.getMaximumPoolSize())
return super.offer(o);

// 执行到这里,表明当前线程数大于核心线程数,并且小于最大线程数。
// 表明是可以创建新线程的,那到底要不要创建呢?分两种情况:

//1. 如果已提交的任务数小于当前线程数,表示还有空闲线程,无需创建新线程
if (parent.getSubmittedCount()<=(parent.getPoolSize()))
return super.offer(o);

//2. 如果已提交的任务数大于当前线程数,线程不够用了,返回 false 去创建新线程
if (parent.getPoolSize()<parent.getMaximumPoolSize())
return false;

// 默认情况下总是把任务添加到任务队列
return super.offer(o);
}

}

只有当前线程数大于核心线程数、小于最大线程数,并且已提交的任务个数大于当前线程数时,也就是说线程不够用了,但是线程数又没达到极限,才会去创建新的线程。这就是为什么 Tomcat 需要维护已提交任务数这个变量,它的目的就是在任务队列的长度无限制的情况下,让线程池有机会创建新的线程。可以通过设置 maxQueueSize 参数来限制任务队列的长度。

jvm性能调试工具调试远程程序

远程调试程序性能

  • 不论使用什么工具调试,都需要jvm开启远程调试功能
  • 本案列使用tomcat运行war包程序进行调试

tomcat开启jvm远程调试

  1. 请参考另外一篇文章tomcat设置启动参数,讲解了如何配置tomcat启动参数

  2. 在脚本中添加

    1
    2
    3
    4
    5
    JAVA_OPTS='-Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false
    -Dcom.sun.management.jmxremote.authenticate=false'
    或者
    JAVA_OPTS=’-Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false
    -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.1.54 其他配置’

    备注:

    1
    2
    3
     在Java启动时,JMX会绑定一个接口,RMI也会绑定一个接口,在复杂网络环境下,有可能你通过打开防火墙允许了JMX端口的通过,但是由于没有放行RMI,远程连接也是会失败的。
    这是因为JMX在远程连接时,会随机开启一个RMI端口作为连接的数据端口,这个端口会被防火墙给阻止,以至于连接超时失败。在Java7u25版本后,
    可以使用 -Dcom.sun.management.jmxremote.rmi.port参数来指定这个端口;好消息是,你可以将这个端口和jmx.port的端口设置成一个端口,这样防火墙就只需要放行一个端口就可以了。
  3. 参数说明

    1
    2
    3
    4
    5
    1. -Dcom.sun.management.jmxremote.port :这个是配置远程 connection 的端口号的,要确定这个端口没有被占用
    2. -Dcom.sun.management.jmxremote.ssl=false 指定了 JMX 是否启用 ssl
    3. -Dcom.sun.management.jmxremote.authenticate=false 指定了JMX 是否启用鉴权(需要用户名,密码鉴权)
    2,3两个是固定配置,是 JMX 的远程服务权限的
    4. -Djava.rmi.server.hostname :这个是配置 server 的 IP 的

android开发/微信开发/android调试

无法安装软件

  1. adb uninstall net.sourceforge.simcpux / 这样可以将手机中的软件删除干净
  2. 移动app支付的时候调用起微信客户端的进行sign的key全部是小写,和公众号的不一样,公众号的驼峰标示。

用电脑版Chrome进行调试

1
2
3
1. 准备已就绪,在chrome地址栏输入“chrome://inspect“即可进入调试页面,对应的设备下方会显示chrome打开的网页和手机上打开的App,网页下方的四个按键分别对应审查、置顶、重载和关闭对应网页,右上方的输入框可输入要打开的网页链接。
2. 打开的app运行在android4.4以上系统,同时使用的事chrome浏览器内核
3. 第一次打开网页的审查会一片空白,这是因为第一次需要翻墙链接

android进行生成签名

1
2
3
4
5
6
7
8
9
10
keytool -genkeypair \
-keyalg RSA \
-keysize 1024 \
-sigalg SHA1withRSA \
-dname "CN=www.xxxx.net, OU=R&D, O=\"xxx xxx Tech Co., Ltd\", L=WenZhou, S=Zhejiang, C=CN" \
-validity 3650 \
-alias (别名) \
-keypass (此处是密码) \
-keystore app.jks \
-storepass (此处是密码)

签名

1
2
3
4
5
6
7
8
9
10
jarsigner -keystore app.jks \
-storepass (此处是上一个填写密码) \
-storetype jks \
-keypass AmEd3ERCxEf \
-digestalg SHA1 \
-sigalg SHA1withRSA \
-tsa http://timestamp.digicert.com \
-signedjar (加密后apk地址)android-release-signed.apk \
(加密前apk地址)android-release-unsigned.apk \
(上面填写的别名)

验证

1
2
3
4
5
6
7
jarsigner -verify \
-keystore app.jks \
-storetype jks \
-tsa http://timestamp.digicert.com \
-digestalg SHA1 \
-sigalg SHA1withRSA \
(加密后apk地址)android-release-signed.apk

es父子文档

es join

概念

  1. es父子文档在6.x后已经变更了说明。去掉了type:parent 改为join概念
  2. es官网文档链接地址
  3. 特别申明,join中注意routing字段。routing参数是强制的,就会抛出错误。
    1
    2
    3
    IndexRequest request = new IndexRequest(INDEX, TYPE,i)
    .source(map)
    .routing("2");
  4. 在嵌套的文档中,实际情况是所有内部的对象集中在同一个分块中的Lucene文档,这对于对象便捷地连接根文档而言,是非常有好处的。父子文档则是完全不同的ES文档,所以只能分别搜索它们,效率更低。对于文档的索引、更新和删除而言,父子的方式就显得出类拔萃了。这是因为父辈和子辈文档都是独立的ES文档,各自管理。举例来说,如果一个分组有很多活动,要增加一个新活动,那么就是增加一篇新的活动文档。如果使用嵌套类型的方式,ES不得不重新索引分组文档,来囊括新的活动和全部已有活动,这个过程就会更慢。

java开发文档规范

代码添加注释

  1. 文档参考处groovy doc文档

  2. 以下自己见解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 在此处添加对整个类的大致说明
    * 添加@符后面会直接添加到文档的说明处
    * @version 1.0
    */
    class Person {
    /** 在这里添加对属性的注释 */
    String name

    /**
    * 对方法的说明,此行开始到@param处一直是方法的说明
    *
    * @param otherPerson ;对参数名的解释,请加(;)符号分割,
    * @return 简单的renturn介绍,到后面都是return的介绍
    * @return 这里也算return的介绍,但是会换行。也可以不加,建议加
    * @throws 方法抛出的异常
    */
    String greet(String otherPerson) {
    // 在方法体内注册 使用 //;使用/** xxx */会是注释和下一个方法无法绑定,
    "Hello ${otherPerson}"
    }
    }
  3. disruptor

idea中调试tomcat代码

idea中调试Tomcat源代码

  1. 代码跟踪时tomcat进入的代码有时候会错误。和正在使用tomcat不符合。在pom下新增以下代码进行指定
    1
    2
    3
    4
    5
    6
    <dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>8.5.35</version>
    <scope>provided</scope>
    </dependency>

java自定义calssloader

自定义classloder

自定义之前需要熟悉的双亲委派机制

  1. 双亲委派是为了保证jvm同时只存在一个Object对象。都由父类加载。父类无法找到才由子类进行查找
  2. 但在web容器中:如tomcat 不能使用这种。每个war包由自己的jar包。这时候就要进行容器隔离。
  3. 所有classloader都有父类加载器。但不是父类。由方法parent指定。
  4. 顶级的Boostrap ClassLoader:这个是由c++实现的,所以在方法区并没有Class对象的实例存在。用于加载JAVA_HOME/bin目录下的jar包
  5. 扩展类加载器(Extension ClassLoader):它负责用于加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量指定所指定的路径中所有类库,开发者可以直接使用扩展类加载器。java.ext.dirs系统变量所指定的路径的可以通过程序来查看。System.getProperty(“java.ext.dirs”)
  6. 应用程序类加载器(Application ClassLoader):负责加载用户类路径上指定的类库。开发者可以直接使用这个类加载器。ps:在没有指定自定义类加载器的情况下,这就是程序的默认加载器。
  7. 自定义类加载器(User ClassLoader):
1
2
3
4
5
6
loadClass()和findClass()方法区别:
自定义时候覆盖loadClass()方法可以保证同一个类存在多份。自行实现类加载
findClass()方法还是先由先由父类进行加载,查看是否加载多。没有加载在交由子类。
场景一:tomcat容器隔离。这时候需要覆盖loadClass()。同一个war包会有同样一个jar包,可以版本不同,能够同时存在.findClass()则无法处理。再一次加载后查看缓存已经存在直接进行加载。
场景二:tomcat中servlet-api.jar以及公共jar包。这个需要在jvm中只有一份。就需要只覆盖findClass()。
tomcat有不同的clasloader加载不同的场景

自定义classloader

  1. 代码链接

java使用jni调用本地

Java调用JNI

调用过程

  1. 首先需要java的方法需要native 做修饰。
  2. 通过javac和javah生成.h文件。
  3. 通过c++编程生成dll文件
  4. 加载生成的DLL库文件.(后面代码详细讲解)

先看看java代码(分是否package)。这里是没有package的

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Map;
public class NativeOfficeConverter {
/**
* 将
* @param path 需要转换的文件路径
* @param outDir 输出的文件目录
* @return 转换后的文件目录
* key有3个: code path imgPath
* code不等于200 转换失败。等于200 path转换后的目录,等于传入的outDir。imgPath等同于path。可自定义
*/
public native Map<String,String> docToHtmlConvert(String path,String outDir);
}
  1. javac -encoding UTF-8 NativeOfficeConverter.java
  2. javah NativeOfficeConverter // 别加class后缀。 .h文件就生成了

有package的

  1. javah com.test.NativeOfficeConverter
    1
    这个命令需要在同com平级的目录上执行,同时.h文件会生成在当前目录。javah中有一个-d可以指定。看help

生成dll库

  1. 放入到java系统中
    1
    2
    3
    4
    5
    6
    // 加载生成的DLL库文件
    static {
    // 2选1 load加载的是全路径。loadLibrary需要dll文件放入到系统环境变量中。预先已经加载
    System.loadLibrary("LibJniTest");
    System.load("LibJniTest");
    }

jstorm安装配置开发

jstorm记录

安装

简介

JStorm集群包含两类节点:主控节点(Nimbus)和工作节点(Supervisor)。其分别对应的角色如下:

  1. 主控节点(Nimbus)上运行Nimbus Daemon。Nimbus负责接收Client提交的Topology,分发代码,分配任务给工作节点,监控集群中运行任务的状态等工作。Nimbus作用类似于Hadoop中JobTracker。
  2. 工作节点(Supervisor)上运行Supervisor Daemon。Supervisor通过subscribe Zookeeper相关数据监听Nimbus分配过来任务,据此启动或停止Worker工作进程。每个Worker工作进程执行一个Topology任务的子集;单个Topology的任务由分布在多个工作节点上的Worker工作进程协同处理。
    1
    2
    3
    Nimbus和Supervisor节点之间的协调工作通过Zookeeper实现。此外,Nimbus和Supervisor本身均为无状态进程,支持Fail Fast;JStorm集群节点的
    状态信息或存储在Zookeeper,或持久化到本地,这意味着即使Nimbus/Supervisor宕机,重启后即可继续工作。这个设计使得JStorm集群具有非常好的
    稳定性。

概念

  • Storm组件和Hadoop组件对比
jstorm hadoop
角色 Nimbus JobTracker
Supervisor TaskTracker
Worker Child
应用名称 Topology Job
编程接口 Spout/Bolt Mapper/Reducer

接下来解释几个重要的概念:

  1. topology

    1
    jstorm所执行的任务,其实就是一个topology,一个topology可以包含多个spout,多个bolt,多级bolt
  2. spout

    1
    流的来源
  3. bolt

    1
    可以说是流的去处(这里可以进行业务处理)

jvm的常用运行参数以及内存分配和垃圾收集器的工作

jvm的运行参数

jvm的配置参数

  1. -Xms40m //初始内存

  2. -Xmx256m //最大内存

  3. -Xmn16m //最小内存

  4. -XX:PermSize=128M // 永久带的内存

  5. -XX:MaxPermSize=256M // 永久带的最大内存(一般默认64MB)

  6. -XX:SurvivorRatio=8 // 新生代中Eden区域与Survivor区域的容量比值
    jvm内存的分配图

  7. -XX:MaxTenuringThreshold=15 // 一般jvm默认值15 晋升到老年代的对象年龄,每个对象坚持一次MinorGC之后,年龄就增加1,当超过这个参数的值时就进入老年代

jvm常用一些配置参数

  1. GC参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -XX:+PrintGC    每次触发GC的时候打印相关日志

    -XX:+PrintGCDetails 更详细的GC日志

    -XX:+PrintHeapAtGC 每次GC时打印堆的详细详细信息

    -XX:+PrintGCApplicationConcurrentTime 打印应用程序执行时间

    -XX:+PrintGCApplicationAtoppedTime 打印应用程序由GC引起的停顿时间

    -XX:+PrintReferenceGC 跟踪系统内的软引用,弱引用,虚引用和finallize队列。
  2. 类跟踪

    1
    2
    3
    4
    5
    6
    7
    -verbose:class    跟踪类的加载和卸载

    -XX:+TraceClassLoading 单独跟踪类加载

    -XX:+TraceClassUnloading 单独跟踪类卸载

    -XX:+PrintClassHistogram 查看运行时类的分布情况,使用时在控制台按ctrl+break
  3. 系统参数查看

    1
    2
    3
    4
    5
    -XX:+PrintVMOptions       运行时,打印jvm接受的命令行显式参数

    -XX:+PrintCommandLineFlags 打印传递jvm的显式和隐式参数

    -XX:+PrintFlagsFinal 打印所有系统参数值
  4. 1
    2
    3
    4
    5
    6
    7
    8
    9
    -Xms    堆初始值

    -Xmx 堆最大可用值

    -Xmn 新生代大小,一般设为整个堆的1/3到1/4左右

    -XX:SurvivorRatio 设置新生代中eden区和from/to空间的比例关系n/1

    -XX:NewRatio 设置老年代与新生代的比
    1. 垃圾回收器
    1
    2
    3
    4
    5
       Concurrent Mark-Sweep GC :CMS回收器

    Mark Sweep Compact GC: 串行GC(Serial GC)

    Parallel GC with 2 thread(s): 并行GC(ParNew)