轉載自:《JMH使用說明》
JMH使用說明
一、概述
JMH,即Java Microbenchmark Harness,是專門用於代碼微基準測試的工具套件。何謂Micro Benchmark呢?簡單的來說就是基於方法層面的基準測試,精度可以達到微秒級。當你定位到熱點方法,希望進一步優化方法性能的時候,就可以使用JMH對優化的結果進行量化的分析。和其他競品相比——如果有的話,JMH最有特色的地方就是,它是由Oracle內部實現JIT的那撥人開發的,對於JIT以及JVM所謂的“profile guided optimization”對基準測試準確性的影響可謂心知肚明(smile)
JMH比較典型的應用場景有:
- 想準確的知道某個方法需要執行多長時間,以及執行時間和輸入之間的相關性;
- 對比接口不同實現在給定條件下的吞吐量;
- 查看多少百分比的請求在多長時間內完成;
二、第一個例子
接下來,我們看看如何使用JMH。
要使用JMH,首先需要準備好Maven環境,JMH的源代碼以及官方提供的Sample就是使用Maven進行項目管理的,github上也有使用gradle的例子可自行搜索參考。使用mvn命令行創建一個JMH工程:
mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=co.speedar.infra \
-DartifactId=jmh-test \
-Dversion=1.0
如果要在現有Maven項目中使用JMH,只需要把生成出來的兩個依賴以及shade插件拷貝到項目的pom中即可:
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>0.7.1</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>0.7.1</version>
<scope>provided</scope>
</dependency>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>microbenchmarks</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
然後,就可以着手寫第一個JMH例子了:
package co.speedar.infra.test;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@BenchmarkMode(Mode.AverageTime) // 測試方法平均執行時間
@OutputTimeUnit(TimeUnit.MICROSECONDS) // 輸出結果的時間粒度爲微秒
@State(Scope.Thread) // 每個測試線程一個實例
public class FirstBenchMark {
private static Logger log = LoggerFactory.getLogger(FirstBenchMark.class);
@Benchmark
public String stringConcat() {
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
log.debug(s);
return s;
}
public static void main(String[] args) throws RunnerException {
// 使用一個單獨進程執行測試,執行5遍warmup,然後執行5遍測試
Options opt = new OptionsBuilder().include(FirstBenchMark.class.getSimpleName()).forks(1).warmupIterations(5)
.measurementIterations(5).build();
new Runner(opt).run();
}
}
在上面的測試代碼中,加了幾個類註解以及一個方法註解,在main方法中指明瞭測試的一些選項,然後使用JMH提供的Runner執行測試。在註釋中提供了大致的講解,具體的選項說明後邊再詳述。接下來我們直接跑起來這個測試看看結果如何。執行測試,可能會遇到報錯:Exception in thread "main" java.lang.RuntimeException: ERROR: Unable to find the resource: /META-INF/BenchmarkList
解決方法:
- 先執行mvn clean install然後再在ide中執行main方法;
-
或者在eclipse中安裝m2e-apt插件,然後啓用Automatically configure JDT APT選項;
然後,就可以愉快地看到測試結果如下:
# JMH 1.14.1 (released 525 days ago, please consider updating!)
# VM version: JDK 1.8.0_91, VM 25.91-b14
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre/bin/java
# VM options: -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: co.speedar.infra.test.FirstBenchMark.stringConcat
# Run progress: 0.00% complete, ETA 00:00:10
# Fork: 1 of 1
# Warmup Iteration 1: 0.009 us/op
# Warmup Iteration 2: 0.011 us/op
# Warmup Iteration 3: 0.007 us/op
# Warmup Iteration 4: 0.006 us/op
# Warmup Iteration 5: 0.006 us/op
Iteration 1: 0.006 us/op
Iteration 2: 0.005 us/op
Iteration 3: 0.005 us/op
Iteration 4: 0.006 us/op
Iteration 5: 0.006 us/op
Result "stringConcat":
0.006 ±(99.9%) 0.001 us/op [Average]
(min, avg, max) = (0.005, 0.006, 0.006), stdev = 0.001
CI (99.9%): [0.005, 0.006] (assumes normal distribution)
# Run complete. Total time: 00:00:10
Benchmark Mode Cnt Score Error Units
FirstBenchMark.stringConcat avgt 5 0.006 ± 0.001 us/op
測試結果表明,被測試方法平均耗時爲0.006微秒,誤差爲±0.001微秒。
三、詳細說明
3.1 基本概念
首先看看JMH的幾個基本概念:
-
Mode
Mode 表示 JMH 進行 Benchmark 時所使用的模式。通常是測量的維度不同,或是測量的方式不同。目前 JMH 共有四種模式:-
Throughput: 整體吞吐量,例如“1秒內可以執行多少次調用”。
-
AverageTime: 調用的平均時間,例如“每次調用平均耗時xxx毫秒”。
-
SampleTime: 隨機取樣,最後輸出取樣結果的分佈,例如“99%的調用在xxx毫秒以內,99.99%的調用在xxx毫秒以內”
-
SingleShotTime: 以上模式都是默認一次 iteration 是 1s,唯有 SingleShotTime 是隻運行一次。往往同時把 warmup 次數設爲0,用於測試冷啓動時的性能。
-
-
Iteration
Iteration 是 JMH 進行測試的最小單位。在大部分模式下,一次 iteration 代表的是一秒,JMH 會在這一秒內不斷調用需要 benchmark 的方法,然後根據模式對其採樣,計算吞吐量,計算平均執行時間等。 -
Warmup
Warmup 是指在實際進行 benchmark 前先進行預熱的行爲。爲什麼需要預熱?因爲 JVM 的 JIT 機制的存在,如果某個函數被調用多次之後,JVM 會嘗試將其編譯成爲機器碼從而提高執行速度。爲了讓 benchmark 的結果更加接近真實情況就需要進行預熱。
3.2 註解與選項
3.2.1 常用註解說明
-
@BenchmarkMode
對應Mode選項,可用於類或者方法上, 需要注意的是,這個註解的value是一個數組,可以把幾種Mode集合在一起執行,還可以設置爲Mode.All,即全部執行一遍。 -
@State
類註解,JMH測試類必須使用@State註解,State定義了一個類實例的生命週期,可以類比Spring Bean的Scope。由於JMH允許多線程同時執行測試,不同的選項含義如下:-
Scope.Thread:默認的State,每個測試線程分配一個實例;
-
Scope.Benchmark:所有測試線程共享一個實例,用於測試有狀態實例在多線程共享下的性能;
-
Scope.Group:每個線程組共享一個實例;
-
-
@OutputTimeUnit
benchmark 結果所使用的時間單位,可用於類或者方法註解,使用java.util.concurrent.TimeUnit中的標準時間單位。 -
@Benchmark
方法註解,表示該方法是需要進行 benchmark 的對象。 -
@Setup
方法註解,會在執行 benchmark 之前被執行,正如其名,主要用於初始化。 -
@TearDown
方法註解,與@Setup 相對的,會在所有 benchmark 執行結束以後執行,主要用於資源的回收等。 -
@Param
成員註解,可以用來指定某項參數的多種情況。特別適合用來測試一個函數在不同的參數輸入的情況下的性能。@Param註解接收一個String數組,在@setup方法執行前轉化爲爲對應的數據類型。多個@Param註解的成員之間是乘積關係,譬如有兩個用@Param註解的字段,第一個有5個值,第二個字段有2個值,那麼每個測試方法會跑5*2=10次。
3.2.2 註解使用例子
以下示例代碼來自JMH官方例子,爲了節省篇幅刪除了頭部的license聲明和重複的註釋。
- @BenchmarkMode和@OutputTimeUnit
public class JMHSample_02_BenchmarkModes {
@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public void measureThroughput() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* Mode.AverageTime measures the average execution time, and it does it
* in the way similar to Mode.Throughput.
*
* Some might say it is the reciprocal throughput, and it really is.
* There are workloads where measuring times is more convenient though.
*/
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureAvgTime() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* Mode.SampleTime samples the execution time. With this mode, we are
* still running the method in a time-bound iteration, but instead of
* measuring the total time, we measure the time spent in *some* of
* the benchmark method calls.
*
* This allows us to infer the distributions, percentiles, etc.
*
* JMH also tries to auto-adjust sampling frequency: if the method
* is long enough, you will end up capturing all the samples.
*/
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureSamples() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* Mode.SingleShotTime measures the single method invocation time. As the Javadoc
* suggests, we do only the single benchmark method invocation. The iteration
* time is meaningless in this mode: as soon as benchmark method stops, the
* iteration is over.
*
* This mode is useful to do cold startup tests, when you specifically
* do not want to call the benchmark method continuously.
*/
@Benchmark
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureSingleShot() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* We can also ask for multiple benchmark modes at once. All the tests
* above can be replaced with just a single test like this:
*/
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime})
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureMultiple() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* Or even...
*/
@Benchmark
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void measureAll() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
/*
* ============================== HOW TO RUN THIS TEST: ====================================
*
* You are expected to see the different run modes for the same benchmark.
* Note the units are different, scores are consistent with each other.
*
* You can run this test:
*
* a) Via the command line:
* $ mvn clean install
* $ java -jar target/benchmarks.jar JMHSample_02 -wi 5 -i 5 -f 1
* (we requested 5 warmup/measurement iterations, single fork)
*
* b) Via the Java API:
* (see the JMH homepage for possible caveats when running from IDE:
* http://openjdk.java.net/projects/code-tools/jmh/)
*/
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_02_BenchmarkModes.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.forks(1)
.build();
new Runner(opt).run();
}
}
- @State
public class JMHSample_03_States {
@State(Scope.Benchmark)
public static class BenchmarkState {
volatile double x = Math.PI;
}
@State(Scope.Thread)
public static class ThreadState {
volatile double x = Math.PI;
}
/*
* Benchmark methods can reference the states, and JMH will inject the
* appropriate states while calling these methods. You can have no states at
* all, or have only one state, or have multiple states referenced. This
* makes building multi-threaded benchmark a breeze.
*
* For this exercise, we have two methods.
*/
@Benchmark
public void measureUnshared(ThreadState state) {
// All benchmark threads will call in this method.
//
// However, since ThreadState is the Scope.Thread, each thread
// will have it's own copy of the state, and this benchmark
// will measure unshared case.
state.x++;
}
@Benchmark
public void measureShared(BenchmarkState state) {
// All benchmark threads will call in this method.
//
// Since BenchmarkState is the Scope.Benchmark, all threads
// will share the state instance, and we will end up measuring
// shared case.
state.x++;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_03_States.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.threads(4)
.forks(1)
.build();
new Runner(opt).run();
}
}
- @Param
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class JMHSample_27_Params {
/**
* In many cases, the experiments require walking the configuration space
* for a benchmark. This is needed for additional control, or investigating
* how the workload performance changes with different settings.
*/
@Param({"1", "31", "65", "101", "103"})
public int arg;
@Param({"0", "1", "2", "4", "8", "16", "32"})
public int certainty;
@Benchmark
public boolean bench() {
return BigInteger.valueOf(arg).isProbablePrime(certainty);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_27_Params.class.getSimpleName())
// .param("arg", "41", "42") // Use this to selectively constrain/override parameters
.build();
new Runner(opt).run();
}
}
3.2.3 常用選項說明
-
include
benchmark 所在的類的名字,這裏可以使用正則表達式對所有類進行匹配。 -
fork
JVM因爲使用了profile-guided optimization而“臭名昭著”,這對於微基準測試來說十分不友好,因爲不同測試方法的profile混雜在一起,“互相傷害”彼此的測試結果。對於每個@Benchmark方法使用一個獨立的進程可以解決這個問題,這也是JMH的默認選項。注意不要設置爲0,設置爲n則會啓動n個進程執行測試(似乎也沒有太大意義)。fork選項也可以通過方法註解以及啓動參數來設置。 -
warmupIterations
預熱的迭代次數,默認1秒。 -
measurementIterations
實際測量的迭代次數,默認1秒。 -
CompilerControl
可以在@Benchmark註解中指定編譯器行爲。- CompilerControl.Mode.DONT_INLINE:This method should not be inlined. Useful to measure the method call cost and to evaluate if it worth to increase the inline threshold for the JVM.
- CompilerControl.Mode.INLINE:Ask the compiler to inline this method. Usually should be used in conjunction with Mode.DONT_INLINE to check pros and cons of inlining.
- CompilerControl.Mode.EXCLUDE:Do not compile this method – interpret it instead. Useful in holy wars as an argument how good is the JIT.
-
Group
方法註解,可以把多個 benchmark 定義爲同一個 group,則它們會被同時執行,譬如用來模擬生產者-消費者讀寫速度不一致情況下的表現。可以參考如下例子:
CounterBenchmark.java -
Level
用於控制 @Setup,@TearDown 的調用時機,默認是 Level.Trial。-
Trial:每個benchmark方法前後;
-
Iteration:每個benchmark方法每次迭代前後;
-
Invocation:每個benchmark方法每次調用前後,謹慎使用,需留意javadoc註釋;
-
-
Threads
每個fork進程使用多少條線程去執行你的測試方法,默認值是Runtime.getRuntime().availableProcessors()。
四、一些值得注意的地方
4.1 無用代碼消除(Dead Code Elimination)
現代編譯器是十分聰明的,它們會對你的代碼進行推導分析,判定哪些代碼是無用的然後進行去除,這種行爲對微基準測試是致命的,它會使你無法準確測試出你的方法性能。JMH本身已經對這種情況做了處理,你只要記住:1.永遠不要寫void方法;2.在方法結束返回你的計算結果。有時候如果需要返回多於一個結果,可以考慮自行合併計算結果,或者使用JMH提供的BlackHole對象:
/*
* This demonstrates Option A:
*
* Merge multiple results into one and return it.
* This is OK when is computation is relatively heavyweight, and merging
* the results does not offset the results much.
*/
@Benchmark
public double measureRight_1() {
return Math.log(x1) + Math.log(x2);
}
/*
* This demonstrates Option B:
*
* Use explicit Blackhole objects, and sink the values there.
* (Background: Blackhole is just another @State object, bundled with JMH).
*/
@Benchmark
public void measureRight_2(Blackhole bh) {
bh.consume(Math.log(x1));
bh.consume(Math.log(x2));
}
4.2 常量摺疊(Constant Folding)
常量摺疊是一種現代編譯器優化策略,例如,i = 320 * 200 * 32,多數的現代編譯器不會真的產生兩個乘法的指令再將結果儲存下來,取而代之的,他們會辨識出語句的結構,並在編譯時期將數值計算出來(i = 2,048,000)。
在微基準測試中,如果你的計算輸入是可預測的,也不是一個@State實例變量,那麼很可能會被JIT給優化掉。對此,JMH的建議是:1.永遠從@State實例中讀取你的方法輸入;2.返回你的計算結果;3.或者考慮使用BlackHole對象;
見如下官方例子:
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class JMHSample_10_ConstantFold {
private double x = Math.PI;
private final double wrongX = Math.PI;
@Benchmark
public double baseline() {
// simply return the value, this is a baseline
return Math.PI;
}
@Benchmark
public double measureWrong_1() {
// This is wrong: the source is predictable, and computation is foldable.
return Math.log(Math.PI);
}
@Benchmark
public double measureWrong_2() {
// This is wrong: the source is predictable, and computation is foldable.
return Math.log(wrongX);
}
@Benchmark
public double measureRight() {
// This is correct: the source is not predictable.
return Math.log(x);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_10_ConstantFold.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.forks(1)
.build();
new Runner(opt).run();
}
}
4.3 循環展開(Loop Unwinding)
循環展開最常用來降低循環開銷,爲具有多個功能單元的處理器提供指令級並行。也有利於指令流水線的調度。例如:
for (i = 1; i <= 60; i++)
a[i] = a[i] * b + c;
可以展開成:
for (i = 1; i <= 60; i+=3)
{
a[i] = a[i] * b + c;
a[i+1] = a[i+1] * b + c;
a[i+2] = a[i+2] * b + c;
}
由於編譯器可能會對你的代碼進行循環展開,因此JMH建議不要在你的測試方法中寫任何循環。如果確實需要執行循環計算,可以結合@BenchmarkMode(Mode.SingleShotTime)和@Measurement(batchSize = N)來達到同樣的效果。參考如下例子:
/*
* Suppose we want to measure how much it takes to sum two integers:
*/
int x = 1;
int y = 2;
/*
* This is what you do with JMH.
*/
@Benchmark
@OperationsPerInvocation(100)
public int measureRight() {
return (x + y);
}
還有這個例子:
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class JMHSample_34_SafeLooping {
/*
* JMHSample_11_Loops warns about the dangers of using loops in @Benchmark methods.
* Sometimes, however, one needs to traverse through several elements in a dataset.
* This is hard to do without loops, and therefore we need to devise a scheme for
* safe looping.
*/
/*
* Suppose we want to measure how much it takes to execute work() with different
* arguments. This mimics a frequent use case when multiple instances with the same
* implementation, but different data, is measured.
*/
static final int BASE = 42;
static int work(int x) {
return BASE + x;
}
/*
* Every benchmark requires control. We do a trivial control for our benchmarks
* by checking the benchmark costs are growing linearly with increased task size.
* If it doesn't, then something wrong is happening.
*/
@Param({"1", "10", "100", "1000"})
int size;
int[] xs;
@Setup
public void setup() {
xs = new int[size];
for (int c = 0; c < size; c++) {
xs[c] = c;
}
}
/*
* First, the obviously wrong way: "saving" the result into a local variable would not
* work. A sufficiently smart compiler will inline work(), and figure out only the last
* work() call needs to be evaluated. Indeed, if you run it with varying $size, the score
* will stay the same!
*/
@Benchmark
public int measureWrong_1() {
int acc = 0;
for (int x : xs) {
acc = work(x);
}
return acc;
}
/*
* Second, another wrong way: "accumulating" the result into a local variable. While
* it would force the computation of each work() method, there are software pipelining
* effects in action, that can merge the operations between two otherwise distinct work()
* bodies. This will obliterate the benchmark setup.
*
* In this example, HotSpot does the unrolled loop, merges the $BASE operands into a single
* addition to $acc, and then does a bunch of very tight stores of $x-s. The final performance
* depends on how much of the loop unrolling happened *and* how much data is available to make
* the large strides.
*/
@Benchmark
public int measureWrong_2() {
int acc = 0;
for (int x : xs) {
acc += work(x);
}
return acc;
}
/*
* Now, let's see how to measure these things properly. A very straight-forward way to
* break the merging is to sink each result to Blackhole. This will force runtime to compute
* every work() call in full. (We would normally like to care about several concurrent work()
* computations at once, but the memory effects from Blackhole.consume() prevent those optimization
* on most runtimes).
*/
@Benchmark
public void measureRight_1(Blackhole bh) {
for (int x : xs) {
bh.consume(work(x));
}
}
/*
* DANGEROUS AREA, PLEASE READ THE DESCRIPTION BELOW.
*
* Sometimes, the cost of sinking the value into a Blackhole is dominating the nano-benchmark score.
* In these cases, one may try to do a make-shift "sinker" with non-inlineable method. This trick is
* *very* VM-specific, and can only be used if you are verifying the generated code (that's a good
* strategy when dealing with nano-benchmarks anyway).
*
* You SHOULD NOT use this trick in most cases. Apply only where needed.
*/
@Benchmark
public void measureRight_2() {
for (int x : xs) {
sink(work(x));
}
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
public static void sink(int v) {
// IT IS VERY IMPORTANT TO MATCH THE SIGNATURE TO AVOID AUTOBOXING.
// The method intentionally does nothing.
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_34_SafeLooping.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.forks(3)
.build();
new Runner(opt).run();
}
}
五、License聲明
文中大部分例子來自JMH官方的實例工程:jmh-samples,基於節省篇幅考慮去掉了頭部的license聲明,現補充如下:
/*
* Copyright (c) 2014, Oracle America, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Oracle nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/