Java 性能调优指南 – 高性能Java

本文主要基于 Java performance tuning tips or everything you want to know about Java performance in 15 minutes的翻译。

这篇指南主要调优 java 代码而不是 JVM 设置。

一、JDK 类

Java 1.7.0_06 String 内部表示的改变

  • 从 Java 1.7.0_06 开始,String.substring 总是为它创建的新字符串创建一个新的底层 char[] 值。这意味着这个方法现在有线性的复杂度,之前是常量的复杂度。这个改变的好处是字符串需要更少的内存 footprint(比以前少8字节),也是避免 String.substring 导致的内存泄露的一个保证。

Java 里二进制序列化的不同方法

  • 写单个字节到直接字节缓存是非常慢的。对写记录 — 大多数情况是单个字节的字段,你应该避免使用直接字节缓存。
  • 如果你有原始数组字段,总是使用bulk方法来处理它们(一次处理一批的)。ByteBuffer 的bulk 方法的性能接近于Unsafe的这些方法。如果你需要存储/加载任何其它原始数组 – 除字节型的,用 ByteBuffer.to[YouType]Buffer.put(array) 方法调用,字节缓存的位置会自动更新。不要在循环里调用 ByteBuffer.put[YouType] 方法。
  • 总是尝试用带本地字节顺序的直接缓存序列化原始数组。直接字节缓存的性能接近于Unsafe且是可移植的。

Java 集合概览

单线程 并发
Lists ArrayList : 一般基于数组
LinkedList :不要使用
Vector:废弃
CopyOnWriteArrayList:很少更新,经常遍历。
Queues/deques ArrayDeque:一般基于数组。
Stack:废弃。
PriorityQueue:有序的检索操作。
ArrayBlockQueue:有界阻塞queue。
ConcurrentLinkedDeque/ConcurrentLinkedQueue:无界链接queue(CAS)。
DelayQueue:在每个元素上带延迟的queue。
LinkedBlockingDeque/LinkedBlockingQueue:可选的有界链接queue(锁)。
LinkedTransferQueue:may transfer elements w/o storing。
PriorityBlockingQueue:并发的PriorityQueue。
SynchronousQueue:实现了Queue接口的Exchanger。
Maps HashMap:一般的map。
EnumMap:enum作为key的。Hashtable:废弃。
IdentityHashMap:用 == 比较键。
LinkedHashMap:保留了插入顺序。
TreeMap:有序的键。
WeakHashMap:可用于缓存。
ConcurrentHashMap:一般的并发map。
ConcurrentSkipListMap:有序的并发map。
Sets HashSet:一般的set。
EnumSet:enum的集合。
BitSet:比特位或稀疏整数的集合。
LinkedHashSet:保留了插入顺序。
TreeSet:有序集合。
ConcurrentSkipListSet:有序并发集合。
CopyOnWriteArraySet:很少更新,经常遍历。

继续阅读

JUC Exchanger

一、概述

Exchanger 可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。

二、算法描述

基本想法是维护一个槽指向一个结点,结点包含一个准备提供(出去)的item和一个等待填充的洞。如果一个到来的“occupying”线程看到槽是空的,CAS结点到那里并等待另一个线程发起交换。第二个“fulfilling”线程看到槽是非空的,把它CAS回空,也通过CAS洞交换item,加上唤醒occupying线程,如果它是阻塞的。在每种情况下CAS可能失败,因为一个槽一开始看起来是非空的,但在CAS时不是,或者相反,所以线程需要重试这些动作。

这个简单方法在只有少量线程使用Exchanger时工作得很好,当大量线程使用单个Exchanger时性能快速恶化,由于CAS竞争。因此我们使用一个“arena, 竞技场”,基本上是一个哈希表,有动态可变数量的槽,每一个槽都可被任意线程执行交换。进入线程基于自己的线程ID选择槽,如果一个进入在它自己选择的槽上CAS失败,它选择一个供替代的槽。如果一个线程成功CAS到一个槽但没有其他线程到达,它尝试其他,前往 0 号槽,那里总是存在如果表收缩的话。控制这个的特殊机制如下:

继续阅读

Java SE 6 故障排除指南 – 附录C、JVM 致命错误日志格式

本文翻译自:http://www.oracle.com/technetwork/java/javase/felog-138657.html
本文内容基于 Java SE 6,HotSpot JVM。

当一个致命错误发生时,一个错误日志将被创建,存储了在致命错误发生时获取到的信息和状态。

注意:这个文件的格式可能随着版本的更新而改变。

C.1 致命错误日志位置

产品标志 -XX:ErrorFile=file 可以指定文件在哪里创建,file 表示了文件位置的完整路径。file 里的子字符串 %% 将被转换为 %%p 将被转换为进程ID。

在下面的例子里,错误日志文件将被写到 /var/log/java 目录、被命名为 java_errorPid.log :java -XX:ErrorFile=/var/log/java/java_error%p.log

如果 -XX:ErrorFile=file 标志没有指定,默认的文件名是 hs_err_pid.log,pid是进程ID。

另外,如果没有指定 -XX:ErrorFile=file 标志,系统尝试在进程的工作目录创建文件。万一不能在工作目录创建文件(空间不足、权限问题或其他问题),文件将在操作系统的临时目录里创建。在Solaris和Linux上临时目录是 /tmp。在Windows上临时目录由环境变量 TMP 指定;如果那个环境变量没有指定,将使用 TEMP 环境变量。

C.2 致命错误日志描述

错误日志包含在致命错误发生时获取到的信息,如果可能,包括下面的信息:

  • 操作异常或激起致命错误的信号
  • 版本信息和配置信息
  • 激起致命错误的线程细节和线程的栈痕迹trace
  • 正在运行的线程列表和它们的状态
  • 堆的概要信息
  • 已加载的本地类库(native libraries)列表
  • 命令行参数
  • 环境变量
  • 操作系统和CPU的细节

注意:在某些情况下,只有这些信息的子集会被输出到错误日志。这在致命错误非常严重以至于错误处理器没法回复并报告所有细节。

错误日志是一个文本文件保护下面的章节:

  • 一个头提供了崩溃的简要描述。
  • 一个章节描述了线程信息。
  • 一个章节描述了进程信息。
  • 一个章节描述了系统信息。

注意:这个致命错误日志描述的格式是基于Java SE 6。格式在不同的发布版之间可能不同。

继续阅读

JUC Semaphore 信号量

概要

一个计数信号量,维护了一组许可。acquire() 方法在许可可用前将阻塞,许可可用时获取,每个release() 方法添加一个许可,潜在地释放一个阻塞的获取线程。

信号量常用于约束访问一些(物理或逻辑)资源的线程数量。

信号量初始化为 1 可以用作互斥锁,更常见的是称为二进制信号量,因为它只有两个状态:有一个许可可用,或没有许可可用。当以这种方式使用时,二进制信号量有个属性,“锁”可以被其他线程而不是属主线程(信号量没有记录属主关系)释放。

公平性属性:公平策略,按FIFO顺序分配许可;非公平策略,允许闯入,即 acquire 调用线程获取许可,而不是已经在等待的线程。

一般地,信号量用于控制资源的访问应当初始化为公平的,来保证没有线程因为不能访问资源而饥饿。当把信号量用于其他同步类型的控制,非公平顺序的吞吐量优势压过公平的考量。

内存一致性效果:线程调用 “release” 之前的动作 happens-before 于成功调用 “acquire” 的其他线程的后续动作。

继续阅读

TCP/IP 协议详解 第4章 ARP:地址解析协议

ARP:地址解析协议,是一个基础协议,它的允许对于应用程序和系统管理员一般是透明的。

引言

当一台主机把以太网数据帧发送到位于同一局域网上的另一台主机时,是根据48bit 的以太网地址来确定目的接口的。设备驱动程序从不检查IP数据报中的目的IP地址。

地址解析为 32bit 的IP地址和数据链路层使用的任何类型的地址 提供映射。ARP为IP地址到对应的硬件地址之间提供动态映射。

ARP在TCP/IP协议族中的位置

apr-in-tcpip

ARP背后的一个基本概念是网络接口有一个硬件地址。在硬件层次上进行的数据帧交换必须有正确的接口地址。知道主机的IP地址并不能让内核发送一帧数据给主机,内核(如以太网驱动程序)必须知道目的端的硬件地址才能发送数据。

继续阅读

JUC CyclicBarrier 可重用屏障

介绍

CyclicBarrier 是一个线程同步工具,用于一组互相等待线程之间的协调,在到达某个临界点之前,这些线程必须互相等待,在通过临界点之后,这些线程是独立的;CountDownLatch 是让一个线程等待其他线程完成某些任务,其他线程之间一直是独立的。

CyclicBarrier 还允许指定一个任务,在所有线程到达临界点时执行,由最后到达的线程执行此任务。

在释放所有线程后,CyclicBarrier 可以通过重置状态来重用,这也是 Cyclic 的来源。

继续阅读

2014-03 杂记

首先要说的是:一转眼,这个博客就启动一年了,今天续费,还是14.99$一年的,人民币93.27¥,BudgetVM 现在支持支付宝付款,更方便了,有意者请点链接,这是广告。

今年的动作太大了,换城市、换工作、旅游,都值得记录下。

换城市

在广州的时候,总感觉空气不好,天空太灰霾,所以想换个沿海的空气好点的。一般也就是珠海或深圳了,珠海的IT企业不如深圳多,城市也小,所以只有选择深圳了。深圳当然也有灰霾,但对于我等屌丝,算无路可逃了吧。

继续阅读

Future 与 FutureTask

Future

来自 Java DOC 文档:Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。

也就是说Future具有这样的特性:

  • 异步执行,可用 get 方法获取执行结果。
  • 如果计算还没完成,get 方法是会阻塞的,如果完成了,是可以多次获取并立即得到结果的。
  • 如果计算还没完成,是可以取消计算的。
  • 可以查询计算的执行状态。

埋两个小问题用于设想下怎么实现Future:

  1. Future在计算完成前阻塞 get 访问,完成后可以自由访问,如何实现 get 方法?
  2. 计算的取消是怎么实现的?被取消的计算会终止执行吗?

继续阅读

TCP/IP 协议详解 第3章 IP:网际协议

IP是TCP/IP协议族中最为核心的协议。所有的TCP、UDP、ICMP以及IGMP数据都以IP数据报格式传输。IP提供了不可靠、无连接的数据报传送服务。

不可靠(unreliable)的意思是它不能保证IP数据报能成功地到达目的地。IP仅提供最好的传输服务。任何可靠性要求必须由上层来提供。

无连接(connectionless)是指IP并不维护任何关于后续数据报的状态信息。每个数据报的处理是相互独立的,这也就是说IP数据报可以不按发送顺序接收。

继续阅读

CopyOnWrite 策略

CopyOnWrite 是用于解决并发读写的一种策略,在Write的时候对共享变量进行Copy,在副本上进行更新,再把更新好的副本原子性地替换原来的共享变量。

实现:

  • 读操作Read不涉及对共享变量的更改,因此不需要同步。
  • 如果有多个线程同时申请Write,在各自拷贝的副本上进行修改,然后更新回共享变量,就会导致某些线程的修改被其他线程覆盖。因此Write必须在同步的情况下进行。

优缺点:

  • 由于修改是在副本上进行,所以修改的同时允许其他线程进行读。
  • 由于需要进行拷贝,当然会存在内存占用的问题。
  • 由于在进行写的过程中仍然允许读,所以数据不是实时一致的,只有在写完成后才一致,也就是最终一致。如果需要实时的一致性,建议使用读写锁。

CopyOnWrite 策略适用于那些读远多于写、且对实时性要求不高的操作,优势在读不需要同步。

更多了解可参考 JUC 里 CopyOnWriteArrayList 的实现。


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。