什么是 JMH
JMH 是 Java Microbenchmark Harness 的缩写。中文意思大致是 “JAVA 微基准测试套件”。首先先明白什么是“基准测试”。百度百科给的定义如下:
基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。
可以简单的类比成我们电脑常用的鲁大师,或者手机常用的跑分软件安兔兔之类的性能检测软件。都是按一定的基准或者在特定条件下去测试某一对象的的性能,比如显卡、IO、CPU之类的。
为什么要使用 JMH
基准测试的特质有如下几种:
①、可重复性:可进行重复性的测试,这样做有利于比较每次的测试结果,得到性能结果的长期变化趋势,为系统调优和上线前的容量规划做参考。
②、可观测性:通过全方位的监控(包括测试开始到结束,执行机、服务器、数据库),及时了解和分析测试过程发生了什么。
③、可展示性:相关人员可以直观明了的了解测试结果(web界面、仪表盘、折线图树状图等形式)。
④、真实性:测试的结果反映了客户体验到的真实的情况(真实准确的业务场景+与生产一致的配置+合理正确的测试方法)。
⑤、可执行性:相关人员可以快速的进行测试验证修改调优(可定位可分析)。
可见要做一次符合特质的基准测试,是很繁琐也很困难的。外界因素很容易影响到最终的测试结果。特别对于 JAVA的基准测试。
有些文章会告诉我们 JAVA是 C++编写的,一般来说 JAVA编写的程序不太可能比 C++编写的代码运行效率更好。但是JAVA在某些场景的确要比 C++运行的更高效。不要觉得天方夜谭。其实 JVM随着这些年的发展已经变得很智能,它会在运行期间不断的去优化。
这对于我们程序来说是好事,但是对于性能测试就头疼的。你运行的次数与时间不同可能获得的结果也不同,很难获得一个比较稳定的结果。对于这种情况,有一个解决办法就是大量的重复调用,并且在真正测试前还要进行一定的预热,使结果尽可能的准确。
除了这些,对于结果我们还需要一个很好的展示,可以让我们通过这些展示结果判断性能的好坏。
JMH官方推荐使用Maven来搭建基准测试的骨架,使用也很简单,使用如下命令来生成maven项目:
mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=org.sample \ #报名
-DartifactId=test \ #项目名
-Dversion=1.0
将生成的maven项目导入到eclipse,开始编写测试案例
1.FullName
import java.io.Serializable;
import lombok.Data;
@Data
public class FullName implements Serializable{
/**
*
*/
private static final long serialVersionUID = -2456390418750978524L;
private String firstName;
private String middleName;
private String lastName;
public FullName() {
}
public FullName(String firstName, String middleName, String lastName) {
this.firstName = firstName;
this.middleName = middleName;
this.lastName = lastName;
}
// 省略getter和setter
@Override
public String toString() {
return "[firstName=" + firstName + ", middleName="
+ middleName + ", lastName=" + lastName + "]";
}
}
2.Person
import java.util.Date;
import java.util.List;
import java.util.Map;
import lombok.Data;
@Data
public class Person {
private String name;
private FullName fullName;
private int age;
private Date birthday;
private List hobbies;
private Map clothes;
private List friends;
// getter/setter省略
@Override
public String toString() {
StringBuilder str = new StringBuilder("Person [name=" + name + ", fullName=" + fullName + ", age="
+ age + ", birthday=" + birthday + ", hobbies=" + hobbies
+ ", clothes=" + clothes + "]\n");
if (friends != null) {
str.append("Friends:\n");
for (Person f : friends) {
str.append("\t").append(f);
}
}
return str.toString();
}
}
3.FastJsonUtil
import com.alibaba.fastjson.JSON;
public class FastJsonUtil {
public static String bean2Json(Object obj) {
return JSON.toJSONString(obj);
}
public static T json2Bean(String jsonStr, Class objClass) {
return JSON.parseObject(jsonStr, objClass);
}
}
4.GsonUtil
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
public class GsonUtil {
private static Gson gson = new GsonBuilder().create();
public static String bean2Json(Object obj) {
return gson.toJson(obj);
}
public static T json2Bean(String jsonStr, Class objClass) {
return gson.fromJson(jsonStr, objClass);
}
public static String jsonFormatter(String uglyJsonStr) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonParser jp = new JsonParser();
JsonElement je = jp.parse(uglyJsonStr);
return gson.toJson(je);
}
}
5.JacksonUtil
import java.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonUtil {
private static ObjectMapper mapper = new ObjectMapper();
public static String bean2Json(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
public static T json2Bean(String jsonStr, Class objClass) {
try {
return mapper.readValue(jsonStr, objClass);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
6.JsonLibUtil
import net.sf.json.JSONObject;
public class JsonLibUtil {
public static String bean2Json(Object obj) {
JSONObject jsonObject = JSONObject.fromObject(obj);
return jsonObject.toString();
}
@SuppressWarnings("unchecked")
public static T json2Bean(String jsonStr, Class objClass) {
return (T) JSONObject.toBean(JSONObject.fromObject(jsonStr), objClass);
}
}
7.执行类JsonSerializeBenchmark
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import com.performance.util.FastJsonUtil;
import com.performance.util.GsonUtil;
import com.performance.util.JacksonUtil;
import com.performance.util.JsonLibUtil;
import entiry.FullName;
import entiry.Person;
@BenchmarkMode(Mode.SingleShotTime) // 吞吐量
//Throughput:吞吐量,每段时间执行的次数
//AverageTime:平均耗时,每次执行的平均耗时
//SampleTime:采样耗时,随机采样执行时间
//SingleShotTime:计算一次的耗时,每次执行中计算耗时
//All:所有指标,上面所有项都测试
@OutputTimeUnit(TimeUnit.SECONDS) // 输出的时间单位,可以到天以及到纳秒
@State(Scope.Benchmark) // 每个测试线程分配一个实例 做基准测试会用到一些变量或者在执行的某个测试操作时执行我们自定义的代码就可以使用state注解
//做基准测试会用到一些变量或者在执行的某个测试操作时执行我们自定义的代码就可以使用state注解
//@Fork(2) // Fork进行的数目 jvm启动的数量,多个jvm可以更好的测试性能
//@Warmup(iterations = 4) // 先预热4轮 Warmup(iterations = 5,time = 1) 预热次数和预热时间
//@Measurement(iterations = 10) // 进行10轮测试 Measurement(iterations = 5,time = 1) 基准测试次数和测试时间
//@Threads(value = 1) // 要启动多少个线程
public class JsonSerializeBenchmark {
/**
* 序列化次数参数
*/
@Param({"1000", "10000", "100000"}) // 定义四个参数,之后会分别对这四个参数进行测试
private int count;
private Person p;
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(JsonSerializeBenchmark.class.getSimpleName())
.forks(1)
.warmupIterations(0)
.build();
Collection results = new Runner(opt).run();
//System.out.println(results.toString());
//ResultExporter.exportResult("JSON序列化性能", results, "count", "秒");
}
@Benchmark //要测试的方法
public void JsonLib() {
for (int i = 0; i < count; i++) {
JsonLibUtil.bean2Json(p);
}
}
@Benchmark //要测试的方法
public void Gson() {
for (int i = 0; i < count; i++) {
GsonUtil.bean2Json(p);
}
}
@Benchmark //要测试的方法
public void FastJson() {
for (int i = 0; i < count; i++) {
FastJsonUtil.bean2Json(p);
}
}
@Benchmark //要测试的方法
public void Jackson() {
for (int i = 0; i < count; i++) {
JacksonUtil.bean2Json(p);
}
}
@Setup // @Setup(Level.Trial) // 初始化方法,在全部Benchmark运行之前进行
//benchmark方法前执行
//Trial:每次基准前执行,一次基准测试包含很多轮
//Invocation:调用测试方法前执行,频率很高
//Iteration:每轮迭代前执行
public void prepare() {
List friends=new ArrayList();
friends.add(createAPerson("小明",null));
friends.add(createAPerson("Tony",null));
friends.add(createAPerson("陈小二",null));
p=createAPerson("邵同学",friends);
}
@TearDown //@TearDown(Level.Trial) // 结束方法,在全部Benchmark运行之后进行
//benchmark方法前执行
//Trial:每次基准前执行,一次基准测试包含很多轮
//Invocation:调用测试方法前执行,频率很高
//Iteration:每轮迭代前执行
public void shutdown() {
}
private Person createAPerson(String name,List friends) {
Person newPerson=new Person();
newPerson.setName(name);
newPerson.setFullName(new FullName("zjj_first", "zjj_middle", "zjj_last"));
newPerson.setAge(24);
List hobbies=new ArrayList();
hobbies.add("篮球");
hobbies.add("游泳");
hobbies.add("coding");
newPerson.setHobbies(hobbies);
Map clothes=new HashMap();
clothes.put("coat", "Nike");
clothes.put("trousers", "adidas");
clothes.put("shoes", "安踏");
newPerson.setClothes(clothes);
newPerson.setFriends(friends);
return newPerson;
}
}
8.执行结果
Benchmark (count) Mode Cnt Score Error Units
JsonSerializeBenchmark.FastJson 1000 ss 0.149 s/op
JsonSerializeBenchmark.FastJson 10000 ss 0.210 s/op
JsonSerializeBenchmark.FastJson 100000 ss 0.749 s/op
JsonSerializeBenchmark.Gson 1000 ss 0.112 s/op
JsonSerializeBenchmark.Gson 10000 ss 0.326 s/op
JsonSerializeBenchmark.Gson 100000 ss 2.477 s/op
JsonSerializeBenchmark.Jackson 1000 ss 0.257 s/op
JsonSerializeBenchmark.Jackson 10000 ss 0.316 s/op
JsonSerializeBenchmark.Jackson 100000 ss 0.794 s/op
JsonSerializeBenchmark.JsonLib 1000 ss 0.343 s/op
JsonSerializeBenchmark.JsonLib 10000 ss 1.784 s/op
JsonSerializeBenchmark.JsonLib 100000 ss 15.714 s/op