怎样理解阻塞非阻塞与同步异步的区别?

“阻塞”与”非阻塞”与”同步”与“异步”不能简单的从字面理解,提供一个从分布式系统角度的回答。
1.同步与异步
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由*调用者*主动等待这个*调用*的结果。

而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。

典型的异步编程模型比如Node.js

举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

2. 阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

还是上面的例子,
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

作者:严肃
链接:https://www.zhihu.com/question/19732473/answer/20851256
来源:知乎

(原创)关于Tomcat默认最大占用内存的一系列问题

今天舍友问我tomcat啥都不改的话,内存最大能占用到多少,我脱口而出四分之一物理内存,然而事实真的是这样吗?

1、查阅官方说明文档

JDK文档传送门,当然全是英文看起来需要一点时间,所以我找来了别人的大概翻译

client模式下,JVM初始和最大堆大小为:
在物理内存达到192MB之前,JVM最大堆大小为物理内存的一半,否则,在物理内存大于192MB,在到达1GB之前,JVM最大堆大小为物理内存的1/4,大于1GB的物理内存也按1GB计算,举个例子,如果你的电脑内存是128MB,那么最大堆大小就是64MB,如果你的物理内存大于或等于1GB,那么最大堆大小为256MB。
Java初始堆大小是物理内存的1/64,但最小是8MB。

server模式下:
与client模式类似,区别就是默认值可以更大,比如在32位JVM下,如果物理内存在4G或更高,最大堆大小可以提升至1GB,,如果是在64位JVM下,如果物理内存在128GB或更高,最大堆大小可以提升至32GB。

2、实践是校验真理的唯一标准

在Linux下,我们可以通过以下命令来查看堆内存的默认最大值

java -XX:+PrintFlagsFinal -version | grep MaxHeapSize

输出如下:

上图是我在2G RAM的虚拟机里的结果,可以看到522190848 Byte = 498 MB,而这虚拟机除了给硬件保留的内存外总内存空间为 2039552 KB ,其四分之一也就是509888KB= 497MB,可以说理论和实际值非常接近,误差可忽略。

然而在Windows上,我没有通过命令行去找出最大堆,而是选择了写个Java类来获取。

package test;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;

public class MaxHeapTest {
    public static void main(String[] args) {
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();  
        MemoryUsage heapMemory= memoryMXBean.getHeapMemoryUsage();  
        long maxHeap = heapMemory.getMax();
        // 单位:字节
        System.out.println(maxHeap +" byte");
    }
}

输出为:

有趣的地方来了,根据换算,3771203584 byte = 3596.5 MB,而我windows的机器上除去硬件保留的内存,可用的有 16178MB,其四分一为 4044.5 MB,和上图的输出值存在不可忽略的大小差异,难道是windows系统特别些?我们继续探索。

3、探索、求真

上述的获取方式不一样,我们先来统一下方式,

在CMD下以同样的方式打印JVM参数,找MaxHeapSize字样:

其中,4242538496 byte = 4046MB,嗯,这个值就才对嘛。

那么现在的问题转变成了,为啥通过java编程方式获取到的最大堆内存会和真实值有出入的?小弟不才,没有了进一步探索的毅力,只想站在前辈的肩膀上看看风景。

这里是别人留下的宝贵经验:传送门2
经过计算和检验,的确差了一个Survivor区的大小,加上这个也就对得上了。

引用别人的结论:通过MemoryMXBean 获取的最大堆内存,应该是JVM可以使用的最大堆内存。因为两个幸存区,采用的是标记拷贝的GC算法,实际分配时,永远有一个幸存区是不能被使用的,所以最大堆内存没有包含这一部分

结尾:

以上结论仅针对Hot Spot虚拟机,其它JVM请自行实验。

本文参考链接:https://segmentfault.com/q/1010000007235579?_ea=1280245

Spring声明式事务管理

在 spring 中一共定义了六种事务传播属性

  • PROPAGATION_REQUIRED — 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • PROPAGATION_SUPPORTS — 支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY — 支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW — 新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED — 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER — 以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED — 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)

Q&A:为何选择Netty

1.Netty 是什么?

Netty 是一个基于 JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞、基于事件驱动、高性能、高可靠性和高可定制性。

2.使用 Netty 能够做什么?

  • 开发异步、非阻塞的 TCP 网络应用程序;
  • 开发异步、非阻塞的 UDP 网络应用程序;
  • 开发异步文件传输应用程序;
  • 开发异步 HTTP 服务端和客户端应用程序;
  • 提供对多种编解码框架的集成,包括谷歌的 Protobuf、JBoss Marshalling、Java 序列化、压缩编解码、XML 解码、字符串编解码等,这些编解码框架可以被用户直接使用;
    提供形式多样的编解码基础类库,可以非常方便的实现私有协议栈编解码框架的二次定制和开发;
  • 基于职责链模式的 Pipeline-Handler 机制,用户可以非常方便的对网络事件进行拦截和定制;
  • 所有的 IO 操作都是异步的,用户可以通过 Future-Listener 机制主动 Get 结果或者由IO 线程操作完成之后主动 Notify 结果,用户的业务线程不需要同步等待;
  • IP 黑白名单控制;
  • 打印消息码流;
  • 流量控制和整形;
  • 性能统计;
  • 基于链路空闲事件检测的心跳检测…

3.Netty 在哪些行业得到了应用?

互联网行业
随着网站规模的不断扩大,系统并发访问量也越来越高,传统基于 Tomcat 等 Web 容器的垂直架构已经无法满足需求,需要拆分应用进行服务化,以提高开发和维护效率。从组网情况看,垂直的架构拆分之后,系统采用分布式部署,各个节点之间需要远程服务调用,高性能的 RPC 框架必不可少,Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用。

典型的应用有:阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信。

游戏行业
无论是手游服务端、还是大型的网络游戏,Java 语言得到了越来越广泛的应用。Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈,非常方便定制和开发私有协议栈。

大数据领域
经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,默认采用 Netty 进行跨节点通信,它的 Netty Service 基于 Netty 框架二次封装实现。
大数据计算往往采用多个计算节点和一个/N个汇总节点进行分布式部署,各节点之间存在海量的数据交换。由于 Netty 的综合性能是目前各个成熟 NIO 框架中最高的,因此,往往会被选中用作大数据各节点间的通信。

4.使用传统的 Socket 开发挺简单的,我为什么要切换到 NIO 进行编程呢?

首先我们看下传统基于同步阻塞 IO(BIO)的线程模型图:

由上图我们可以看出,传统的同步阻塞 IO 通信存在如下几个问题:

  • 线程模型存在致命缺陷:一连接一线程的模型导致服务端无法承受大量客户端的并发连接;
  • 性能差:频繁的线程上下文切换导致 CPU 利用效率不高;
  • 可靠性差:由于所有的 IO 操作都是同步的,所以业务线程只要进行 IO 操作,也会存在被同步阻塞的风险,这会导致系统的可靠性差,依赖外部组件的处理能力和网络的情况。
  • 采用非阻塞 IO(NIO)之后,同步阻塞 IO 的三个缺陷都将迎刃而解:
  • Nio 采用 Reactor 模式,一个 Reactor 线程聚合一个多路复用器 Selector,它可以同时注册、监听和轮询成百上千个 Channel,一个 IO 线程可以同时并发处理N个客户端连接,线程模型优化为1:N(N < 进程可用的最大句柄数)或者 M : N (M通常为 CPU 核数 + 1, N < 进程可用的最大句柄数);
  • 由于 IO 线程总数有限,不会存在频繁的 IO 线程之间上下文切换和竞争,CPU 利用率高;
  • 所有的 IO 操作都是异步的,即使业务线程直接进行 IO 操作,也不会被同步阻塞,系统不再依赖外部的网络环境和外部应用程序的处理性能。

由于切换到 NIO 编程之后可以为系统带来巨大的可靠性、性能提升,所以,目前采用 NIO 进行通信已经逐渐成为主流。

5.为什么不直接基于 JDK 的 NIO 类库编程呢?

我们通过 JDK NIO 服务端和客户端的工作时序图来回答下这个问题

 

即便抛开代码和 NIO 类库复杂性不谈,一个高性能、高可靠性的 NIO 服务端开发和维护成本都是非常高的,开发者需要具有丰富的 NIO 编程经验和网络维护经验,很多时候甚至需要通过抓包来定位问题。也许开发出一套 NIO 程序需要 1 个月,但是它的稳定很可能需要 1 年甚至更长的时间,这也就是为什么我不建议直接使用 JDK NIO 类库进行通信开发的一个重要原因。

下面再一起看下 JDK NIO 客户端的通信时序图(它同样非常复杂)

 

6.为什么要选择 Netty 框架?

Netty 是业界最流行的 NIO 框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如 Hadoop 的 RPC 框架 Avro 使用 Netty 作为通信框架。很多其它业界主流的 RPC 和分布式服务框架,也使用 Netty 来构建高性能的异步通信能力。

Netty 的优点总结如下:

  • API 使用简单,开发门槛低;
  • 功能强大,预置了多种编解码功能,支持多种主流协议;
  • 定制能力强,可以通过 ChannelHandler 对通信框架进行灵活的扩展;
  • 性能高,通过与其它业界主流的 NIO 框架对比,Netty 的综合性能最优;
  • 社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复,同时,更多的新功能会被加入;
  • 经历了大规模的商业应用考验,质量得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它完全满足不同行业的商用标准。

配置静态常量成SpringBean的几种方法

有时候为了方便,我们会统一在一些类中放置一些固定不变的常量,成为常量公共类。或者我们仅仅是需要将一些某个类的某个静态方法的返回值或者静态属性注入到Spring容器中以供其他Bean使用。

下面来记录一些在网络上找到的方法。

对于传统Spring项目,使用XML配置式

<bean class="cn.ultrasoftware.util.Speaker">
   <property name="printStream" ref="TARGET" />
</bean>

这里就开始有点意思了,Spring提供了以下几种写法


<bean id="TARGET" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
      <property name="staticField" value="java.lang.System.out"/>
</bean>

还有这种写法

<util:constant id="TARGET" static-field="java.lang.System.out" />

最后这种写法我就很少见了

<bean class="cn.ultrasoftware.util.Speaker">
 <property name="printStream" value="#{T(System).out}"/>
</bean>

至于Springboot的写法,没啥特别的,就不说了。

JVM内存分布

本文内容参考《深入理解Java虚拟机》(第2版)一书,内容仅对 JDK1.7及以前版本负责

jvm中内存划分:

线程共享区域为:
1. java堆
2. 方法区
线程私有区域为:
3. JVM栈
4. 本地方法栈
5. 程序计数器
    java堆是jvm内存管理中最大的一块,线程共享。在jvm启动的时候创建。此区域唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。但是随着JIT编译器(即时编译器)的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙变化(对象可能会分配到栈上),所以这种所有对象都分配在堆上也不是那么绝对的。
    java堆细分为新生代和老年代,新生代又分为Eden空间、From Survivor空间、To Survivor空间,新生代中垃圾回收算法为复制算法,复制算法是先将内存分为连个部分,一部分用来放入对象,而另一部分暂时不用,当使用的一部分内存要进行垃圾回收的时候会将不需要回收的对象复制保存在另一个空间中,然后再对使用过的那部分区域进行垃圾回收,这样虽然效率很高,但是很浪费空间,所以一般将新生代分为Eden空间和两个Survivor空间,其大小在HotSpot虚拟机中默认比例为8:1:1,这样在新生代中采用复制算法回收垃圾效率就很高了,具体回收过程是将Eden区域和From Survivor区域作为对象的存储空间,当要进行垃圾回收的时候先将这两个区域中不需要回收的对象复制保存在To Survivor区域中,然后再进行垃圾回收。另外有一点是当一个对象在Eden区域和From Survivor区域中存储的时候发现内存不足,这时会进行内存分配担保,就是将此对象直接存入在老年代中。
    老年代中采用的GC算法为标记-清除算法或者标记-整理算法。标记-清除算法为:首先标记出要进行GC的对象,标记完成后再进行GC。这种算法效率不高,并且会产生很多内存碎片。标记-整理算法:同样是先对要进行GC的对象进行标记,但是不同的是在标记完成后不是立刻执行GC,而是先将不需要GC的对象移动到一端,然后在边界外再对要回收的对象进行GC。
    关于对象的分配:对象优先在Eden区域分配,大对象会直接进入老年代,长期存活的对象会进入老年代,这里的长期存活是根据新生代中的对象年龄阈值来定义的,对象刚分配到新生代的时候年龄为1,每进行一次GC对象的年龄会加1,HotSpot中默认的阈值是15,也就是说对象年龄达到15岁的时候会被分配到老年区,这个值是可以通过参数配置的。
    在进行垃圾回收的时候新生代GC又叫minor GC,老年代GC可以设置内存容量达到百分比的多少的时候进行GC,老年代的GC又叫Full GC,minor GC时间短,频率高,而Full GC时间长,频率低。
2、方法区
    方法区又被称为永久区,线程共享,是用来存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区为堆的一个逻辑部分,但是在JDK1.7的HotSpot中已经将方法区中的字符串常量池移出,JDK1.8已经去除了方法区。
    这个区域很少进行垃圾回收,回收目标主要是针对常量池的回收和对类型的卸载。
3、JVM栈
    JVM栈是线程私有的,它的生命周期与线程相同。JVM栈描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
    局部变量表中存放了编译期可知的各种基本数据类型、对象的引用类型。局部变量表中需要的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
4、本地方法栈
    本地方法栈和JVM栈非常相似,它们之间的区别不过是jvm栈是为执行java方法服务,而本地方法栈是为jvm使用到对的本地方法服务。HotSpot虚拟机中直接把本地方法栈和JVM栈合二为一了。
5、程序计数器
    程序计数器是一块较小的内存空间,线程私有。它可以看作是当前线程所执行的字节码的行号指示器。在jvm的概念模型里,字节码解释器工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的jvm字节码指令的地址;如果正在执行的是本地方法,这个计数器值则为空。
总结:
    在jvm划分的内存区域中JVM栈和本地方法栈可能会抛出StackOverflowError异常和OutOfMemoryError异常。java堆和方法区可能会抛出OutOfMemoryError异常。程序计数器中没有地方规定会抛出这两个异常。
扩展:
    在jvm规范中,StackOverflowError异常为:如果线程请求的栈深度大于JVM允许的栈深度,将抛出StackOverflowError异常。OutOfMemoryError异常:如果jvm可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
    HotSpot虚拟机中标记要清除的对象方法不是使用引用计数器(有引用的时候计数器+1,引用失效-1,应用为0时回收),而使用的是可达性分析算法:以“GC Roots“的对象为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明这个对象不可达,即这个对象不可用,所以这个对象会被判定为是可回收对象。

(转载)11种将InputStream转换成String的方法以及性能分析

作者:Viacheslav Vedenin
原文链接:Read/convert an InputStream to a String

从其他回答中总结出了11种能将InputStream转换成String的方法(如下),并且对所有方法进行了性能测试(对比结果如下):

将InputStream转换成String的方法:

1.使用 IOUtils.toString (Apache Utils)

String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);

2.使用 CharStreams (guava)

String result = CharStreams.toString(new InputStreamReader(
      inputStream, Charsets.UTF_8));

3.使用 Scanner (JDK)

Scanner s = new Scanner(inputStream).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : "";

4.使用 Stream Api (Java 8)

String result = new BufferedReader(new InputStreamReader(inputStream))
  .lines().collect(Collectors.joining("\n"));

5.使用 parallel Stream Api (Java 8)

String result = new BufferedReader(new InputStreamReader(inputStream)).lines()
   .parallel().collect(Collectors.joining("\n"));

6.使用 InputStreamReader 和 StringBuilder (JDK)

final int bufferSize = 1024;
final char[] buffer = new char[bufferSize];
final StringBuilder out = new StringBuilder();
Reader in = new InputStreamReader(inputStream, "UTF-8");
for (; ; ) {
    int rsz = in.read(buffer, 0, buffer.length);
    if (rsz < 0)
        break;
    out.append(buffer, 0, rsz);
}
return out.toString();

7.使用 StringWriter 和 IOUtils.copy (Apache Commons)

StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, "UTF-8");
return writer.toString();

8.使用 ByteArrayOutputStream 和 inputStream.read (JDK)

ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
    result.write(buffer, 0, length);
}
return result.toString("UTF-8");

9.使用 BufferedReader (JDK)

String newLine = System.getProperty("line.separator");
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder result = new StringBuilder();
String line; boolean flag = false;
while ((line = reader.readLine()) != null) {
    result.append(flag? newLine: "").append(line);
    flag = true;
}
return result.toString();

10.使用 BufferedInputStream 和 ByteArrayOutputStream (JDK)

BufferedInputStream bis = new BufferedInputStream(inputStream);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int result = bis.read();
while(result != -1) {
    buf.write((byte) result);
    result = bis.read();
}
return buf.toString();

11.使用 inputStream.read() 和 StringBuilder (JDK)

int ch;
StringBuilder sb = new StringBuilder();
while((ch = inputStream.read()) != -1)
    sb.append((char)ch);
reset();
return sb.toString();

注意:
1.方法4、5和9会将不同的换行符(例如:\r\n)全转换成\n。
2.方法11在Unicode编码下不能正确地运行。

性能测试

1.针对较短String的性能测试(长度为175)如下所示,代码贴在了github上(模式是测试平均时间,系统是Linux,其中最好的分数是1,343 )

              Benchmark                        Mode  Cnt   Score   Error  Units
8. ByteArrayOutputStream and read (JDK)        avgt   10   1,343 ± 0,028  us/op
6. InputStreamReader and StringBuilder (JDK)   avgt   10   6,980 ± 0,404  us/op
10.BufferedInputStream, ByteArrayOutputStream  avgt   10   7,437 ± 0,735  us/op
11.InputStream.read() and StringBuilder (JDK)  avgt   10   8,977 ± 0,328  us/op
7. StringWriter and IOUtils.copy (Apache)      avgt   10  10,613 ± 0,599  us/op
1. IOUtils.toString (Apache Utils)             avgt   10  10,605 ± 0,527  us/op
3. Scanner (JDK)                               avgt   10  12,083 ± 0,293  us/op
2. CharStreams (guava)                         avgt   10  12,999 ± 0,514  us/op
4. Stream Api (Java 8)                         avgt   10  15,811 ± 0,605  us/op
9. BufferedReader (JDK)                        avgt   10  16,038 ± 0,711  us/op
5. parallel Stream Api (Java 8)                avgt   10  21,544 ± 0,583  us/op

2.针对较长String的性能测试(长度为50100)如下所示,代码贴在了github上(模式是测试平均时间,系统是Linux,其中最好的分数是200,715 )

              Benchmark                        Mode  Cnt   Score        Error  Units
8. ByteArrayOutputStream and read (JDK)        avgt   10   200,715 ±   18,103  us/op
1. IOUtils.toString (Apache Utils)             avgt   10   300,019 ±    8,751  us/op
6. InputStreamReader and StringBuilder (JDK)   avgt   10   347,616 ±  130,348  us/op
7. StringWriter and IOUtils.copy (Apache)      avgt   10   352,791 ±  105,337  us/op
2. CharStreams (guava)                         avgt   10   420,137 ±   59,877  us/op
9. BufferedReader (JDK)                        avgt   10   632,028 ±   17,002  us/op
5. parallel Stream Api (Java 8)                avgt   10   662,999 ±   46,199  us/op
4. Stream Api (Java 8)                         avgt   10   701,269 ±   82,296  us/op
10.BufferedInputStream, ByteArrayOutputStream  avgt   10   740,837 ±    5,613  us/op
3. Scanner (JDK)                               avgt   10   751,417 ±   62,026  us/op
11.InputStream.read() and StringBuilder (JDK)  avgt   10  2919,350 ± 1101,942  us/op

3.性能测试图(下图主要描述了在Windows7系统下,随着Input Stream长度变化各个方法平均时间的变化)

enter image description here

4.性能测试数据(同样是在Windows7系统下,随着Input Stream长度变化各个方法平均时间的变化)

 length  182    546     1092    3276    9828    29484   58968

 test8  0.38    0.938   1.868   4.448   13.412  36.459  72.708
 test4  2.362   3.609   5.573   12.769  40.74   81.415  159.864
 test5  3.881   5.075   6.904   14.123  50.258  129.937 166.162
 test9  2.237   3.493   5.422   11.977  45.98   89.336  177.39
 test6  1.261   2.12    4.38    10.698  31.821  86.106  186.636
 test7  1.601   2.391   3.646   8.367   38.196  110.221 211.016
 test1  1.529   2.381   3.527   8.411   40.551  105.16  212.573
 test3  3.035   3.934   8.606   20.858  61.571  118.744 235.428
 test2  3.136   6.238   10.508  33.48   43.532  118.044 239.481
 test10 1.593   4.736   7.527   20.557  59.856  162.907 323.147
 test11 3.913   11.506  23.26   68.644  207.591 600.444 1211.545

Nginx开启HTTPS,跑步进入HTTP/2.0时代~

  • HTTPS可以理解成工作在SSL层以上的HTTP协议,相对传统的HTTP,HTTPS有很多实用而重要的功能,例如加密传输,有效防止运营商网络劫持,同时一些新型的HTTP算法也只有在HTTPS下才可使用
  • 但是HTTPS增加了握手包的处理,对服务器而言自然要占用更多一些的资源,但是相对于它带来的好处,HTTPS协议还是很值得推广的。为了缓解HTTPS带来的性能下降问题,HTTP/2提供了一种多路复用的解决方案,在单个连接上实现同时进行多个业务单元数据的传输,从而提供页面的并发性能。
  • 值得注意的是,目前HTTP/2仍然没有通过国际ISO标准委员会,所以严格来说,我们讨论的其实是HTTPS 结合 SPDY 草案2。

闲话说完了,下面开始介绍nginx下开启HTTPS支持进而支持HTTP 2.0的方法。

  • 四步走配置nginx开启HTTPS

  1. 首先,你得有个域名和域名对应的SSL证书,通常不建议用自行签发的证书,因为可用性几乎为0。国内的话建议在腾讯云或者阿里云上申请免费的域名SSL证书,有效期为1年,足够用了(域名要通过备案啥的我就不用说了哈)
  2. 点击下载,能下载到一个zip压缩包,里面包含了主流WEB服务器的SSL证书格式。假设下载到的压缩包为php.ultrasoftware.cn.zip ,用WinSCP之类的工具将此压缩包上传到你nginx所在的服务器并解压,命令如下:
unzip php.ultrasoftware.cn.zip

可以看到解压出来的目录有一个名为Nginx的,将该目录下的两个文件复制到你nginx配置文件所在位置,例如/etc/nginx/或者/usr/local/nginx/conf


编号断了,我只好重新编号

  1. Nginx文件夹内获得SSL证书文件 1_www.domain.com_bundle.crt 和私钥文件 2_www.domain.com.key。
  2. 修改配置文件中的内容,例如 vim /etc/nginx/nginx.conf
server {
        listen 443;
        server_name www.domain.com; #填写绑定证书的域名
        ssl on;
        ssl_certificate 1_www.domain.com_bundle.crt;
        ssl_certificate_key 2_www.domain.com.key;
        ssl_session_timeout 5m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照这个套件配置
        ssl_prefer_server_ciphers on;
        location / {
            root   html; #站点目录
            index  index.html index.htm;
        }
    }

然后运行

nginx -t -s reload

如果提示配置没有出错,那么就已经成功配置nginx开启HTTPS了


  • 配置nginx开启HTTP2.0

  1. 最简单的情况:只需要在上述基础上修改一点点就行,具体为
    listen 443 ssl spdy;
  2. 但是现实往往没那么简单,因为RPM包安装的nginx默认不包含HTTP2模块,所以用nginx -t检查配置的时候会报以下的错
    nginx: [warn] invalid parameter "spdy": ngx_http_spdy_module was superseded by ngx_http_v2_module in /etc/nginx/nginx.conf:18

    对于这种情况,唯一的办法是在官网下载对应版本的nginx源码进行升级编译安装,configure时应加上–with-http_spdy_module,这超出了本文的内容,因此不再此细讲。

  3. 注意:从1.9.5版本开始, ngx_http_v2_module 取代http_spdy_module,所以此时应把参数修正为–with-http_v2_module,同样地配置文件修改处为
listen 443 ssl http2;

Centos 下部署并优化Tomcat

  • 在上面几篇文章的基础上,本文将主要教新手如何在Centos系统上部署并优化tomcat

  • 首先,访问Apache Tomcat官网,下载合适的Tomcat版本的压缩包,一般推荐下载Tomcat 7.0或者Tomcat 8.0,如非特殊需求,尽量不要下载Tomcat 8.5和尚处于测试阶段的Tomcat 9.0。这里以下载tomcat 7.0.79为例
# wget http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-7/v7.0.79/bin/apache-tomcat-7.0.79.tar.gz

解压apache-tomcat-7.0.79.tar.gz

# tar -xvf apache-tomcat-7.0.79.tar.gz

类比Windows上的tomcat 启动,通过bin目录下startup.sh脚本来启动tomcat

# cd apache-tomcat-7.0.79/bin
# ./startup.sh && tail -f ../logs/catalina.out

可以从窗口中看到平常熟悉的日志打印,稍等片刻,tomcat即可启动完成,用浏览器打开服务器的ip:8080就能看到那只猫的logo了

  • 优化tomcat启动

# vim $JAVA_HOME/jre/lib/security/java.security

或者

# vim $JRE_HOME/lib/security/java.security

查找securerandom.source=file:/dev/random,改为

securerandom.source=file:/dev/urandom

:wq保存并退出

  • 优化Tomcat对请求的处理能力

Tomcat Connector支持三种方式运行:BIO,NIO,APR。

  • BIO:
一个线程处理一个请求。缺点:并发量高时,线程数较多,浪费资源。
Tomcat7或以下,在Linux系统中默认使用这种方式。
  • NIO:
利用Java的异步IO处理,可以通过少量的线程处理大量的请求。
Tomcat8在Linux系统中默认使用这种方式。
Tomcat7必须修改Connector配置来启动:
  • APR:
即Apache Portable Runtime,从操作系统层面解决io阻塞问题。
Tomcat7或Tomcat8在Win7或以上的系统中启动默认使用这种方式。
Linux如果安装了apr和native,Tomcat直接启动就支持apr
传统意义上Linux下Tomcat想开启APR模式需进行较多编译工作,中间涉及到的知识点过于广泛,所以下文直接使用centos源中编译好了的库来使Tomcat开启APR模式
# yum install tcnative
重启tomcat,看到控制台显示 “Starting ProtocolHandler [“http-apr-8080”]”即为优化成功!

Linux下配置Oracle JDK(Centos/Ubuntu)

 

OpenJDK和Oracle JDK在细节上的区别有哪些,这个请自行百度。不过为了与Windows开发环境相对应,本篇文章主要介绍如何在Centos/Ubuntu下安装配置Oracle JDK

  • 下载安装JDK

首先,到Java官网下载适合自己平台的JDK版本,当前的最新版本为JDK 8u144

一般可以下载到的格式有以下几种:

exe : windows操作系统专用的安装包,除了JDK和JRE外还带有一些管理工具

rpm : Red Hat Linux系列的安装包,可直接用于Centos发行版和Fedora发行版

tar.gz : Linux通用压缩文件版,里面带有JDK和JRE,适用于一切Linux发行版(需自行配置相关环境变量)

Centos :

# wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie"  http://download.oracle.com/otn-pub/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/jdk-8u144-linux-x64.rpm
# rpm -ivH jdk-8u144-linux-x64.rpm

这样,Centos的JDK环境就算安装好了

Ubuntu :

# wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie"  http://download.oracle.com/otn-pub/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/jdk-8u144-linux-x64.tar.gz
# mkdir /usr/java/
# tar -xvf jdk-8u144-linux-x64.tar.gz -C /usr/java

上述操作已经把压缩包下载并解压到了/usr/java目录下,对Ubuntu而言我们还需要继续进行最后一步,也就是环境变量的配置。

# ls /usr/java
jdk1.8.0_144
# nano /etc/profile

在profile结尾处添加以下内容:

JAVA_HOME=/usr/java/jdk1.8.0_144
export CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

使修改生效

# source /etc/profile

到此为了,Ubuntu的JDK环境也配置好了

  • 检验JDK是否可用

# java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b11, mixed mode)

大功告成!