开源Java诊断工具Arthas推荐

Arthas 是Alibaba开源的Java诊断工具,初步试用了一下,甚是方便,在生产线上找问题应该比较方便。


Arthas可以帮助你解决什么问题?

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?

Arthas支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

快速安装

远程到生产的Linux主机上,一行代码即可下载工具包。

1
wget https://alibaba.github.io/arthas/arthas-boot.jar


简单轮询算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main(args: Array<String>) {
val list = arrayListOf(0, 1, 2)

var prevIndex = -1
for (value in (1..7)) { // 执行7次测试
val currentIndex = (prevIndex + 1) % list.size

print("${list[currentIndex]} \t")

prevIndex = currentIndex
}
}

// 0 1 2 0 1 2 0
// TODO 加权

RestTemplate 跳过SSL证书验证

在使用RestTemplate请求接口的过程中,遇到HTTPS请求又没有证书的情况,只能通过配置来忽略证书验证了

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.ewei.custom.yto.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.ClientHttpRequestFactory
import org.springframework.http.client.SimpleClientHttpRequestFactory
import org.springframework.web.client.RestTemplate
import java.net.HttpURLConnection
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager


/**
* @author wuwenze
* @date 2019-06-21
*/
@Configuration
class RestTemplateClientConfig {

@Bean
fun restTemplate(factory: ClientHttpRequestFactory): RestTemplate {
return RestTemplate(factory)
}

@Bean
fun simpleClientHttpRequestFactory(): ClientHttpRequestFactory {
val factory = SkipSSLSimpleClientHttpRequestFactory()
factory.setReadTimeout(30000)
factory.setConnectTimeout(30000)
return factory
}

class SkipSSLSimpleClientHttpRequestFactory : SimpleClientHttpRequestFactory() {
override fun prepareConnection(connection: HttpURLConnection, httpMethod: String) {
if (connection is HttpsURLConnection) {
try {
connection.setHostnameVerifier { _, _ -> true }
connection.sslSocketFactory = createSslSocketFactory()
} catch (e: Throwable) {
// ignore
}
}
super.prepareConnection(connection, httpMethod)
}

private fun createSslSocketFactory(): SSLSocketFactory {
val context: SSLContext = SSLContext.getInstance("TLS")
context.init(null, arrayOf(SkipX509TrustManager()), SecureRandom())
return context.socketFactory
}

class SkipX509TrustManager : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
}
}
}

Kotlin 单例模式详解

单例模式很熟悉了,在Java中有各种创建姿势,算是比较麻烦的,这里就不再赘述了。

Object

那么如何在kotlin中实现单例模式呢?请看代码

1
2
object JsonObjectMapper {
}

仅需简单的把class关键字替换为object就完成了!

1
2
3
4
5
// Kotlin 调用
JsonObjectMapper

// Java调用
JsonObjectMapper.INSTANCE

究竟Kotlin封装了什么细节?让我们一探究竟吧:
通过Tools > Kotlin > Show Kotlin Bytecode 显示字节码,看看翻译后的Java代码
image.png

image.png

翻译成Java后,发现就是一个最简单的饿汉式单例而已。

Companion Object

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SingletonDemo private constructor() {
companion object {
private var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo()
}
return field
}
fun get(): SingletonDemo{
return instance!!
}
}
}

线程安全的懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SingletonDemo private constructor() {
companion object {
private var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo()
}
return field
}
@Synchronized
fun get(): SingletonDemo{
return instance!!
}
}
}

双重校验锁式(Double Check)

1
2
3
4
5
6
class SingletonDemo private constructor() {
companion object {
val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SingletonDemo() }
}
}

静态内部类式

1
2
3
4
5
6
7
8
class SingletonDemo private constructor() {
companion object {
val instance = SingletonHolder.holder
}
private object SingletonHolder {
val holder = SingletonDemo()
}
}

解决HttpServletRequest.inputStream复用问题

众所周知,request对象中的inputStream只能读取一次,下次再读就没有了,在一些场景中,这种特性是不适用的:比如需要在拦截器中读取请求体,然后做相关的参数校验,做完校验后请求打到Controller中就读取不到了。

解决的方法很简单,通过HttpServletRequestWrapperHttpServletRequest对象包装一下,保存requestBody的副本,最后通过过滤器将包装后的request对象替换掉。

附完整代码:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* 包装HttpServletRequest,使其inputStream可复用。
* @author wuwenze
* @date 2019-06-21
*/
@Configuration
@Order(1)
@WebFilter(urlPatterns = ["/**"])
class HttpServletRequestWrapperFilter : Filter {
override fun doFilter(req: ServletRequest?, resp: ServletResponse?, chain: FilterChain?) {
when (req) {
is HttpServletRequest -> chain?.doFilter(MyHttpServletRequestWrapper(req), resp)
else -> chain?.doFilter(req, resp)
}
}

class MyHttpServletRequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) {
private var body: ByteArray = request.getBodyString().toByteArray(Charset.forName("UTF-8"))

@Throws(IOException::class)
override fun getReader(): BufferedReader {
return BufferedReader(InputStreamReader(inputStream))
}

@Throws(IOException::class)
override fun getInputStream(): ServletInputStream {
val byteArrayInputStream = ByteArrayInputStream(body)
return object : ServletInputStream() {

@Throws(IOException::class)
override fun read(): Int {
return byteArrayInputStream.read()
}

override fun isFinished(): Boolean {
return false
}

override fun isReady(): Boolean {
return false
}

override fun setReadListener(readListener: ReadListener) {

}
}
}
}
}

获取requestBody的扩展函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun HttpServletRequest.getBodyString(): String {
val result = StringBuffer()
var reader: BufferedReader? = null
try {
var line: String? = null
reader = BufferedReader(InputStreamReader(this.inputStream, Charset.forName("UTF-8")))
while ({ line = reader.readLine();line }() != null) {
result.append(line)
}
} catch (e: IOException) {
// ignore
} finally {
reader?.close()
}
return result.toString()
}

这样就能多次使用了,每次读取的其实是存储在MyHttpServletRequestWrapper中的副本。

JDK#UUID 简单优化

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class UuidUtil {
private final static char[] DIGITS_ARRAY = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
// 支持的最小进制数
private static final int MIN_RADIX = 2;
// 支持的最大进制数
private static final int MAX_RADIX = DIGITS_ARRAY.length;

public static String randomUuid() {
// 产生UUID
UUID uuid = UUID.randomUUID();

// 分区转换(19位) + 当前时间戳(13位)
return new StringBuffer()//
.append(digits(uuid.getMostSignificantBits() >> 32, 8))//
.append(digits(uuid.getMostSignificantBits() >> 16, 4))//
.append(digits(uuid.getMostSignificantBits(), 4))//
.append(digits(uuid.getLeastSignificantBits() >> 48, 4))//
.append(digits(uuid.getLeastSignificantBits(), 12))//
.append(System.currentTimeMillis()).toString();
}

/**
* Test: 模拟并发场景下的UUID效率,唯一性。
*
* @param args
*/
public static void main(String[] args) {
final long begin = System.currentTimeMillis();
final int threadCount = 100;
final int uuidCountPerThread = 10000;

final ExecutorService executor = Executors.newCachedThreadPool();
final Set<String> uuidSet = new ConcurrentSkipListSet<String>();

for (int i = 0; i < threadCount; ++i) {
executor.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < uuidCountPerThread; ++j) {
uuidSet.add(randomUuid());
}
}
});
}
executor.shutdown();
while (true) {
if (executor.isTerminated()) {
int forecastSum = (threadCount * uuidCountPerThread);
int actualSum = uuidSet.size();
System.out.println(String.format("预计生成%d个,实际生成%d个,重复个数:%d, 耗时:%d",//
forecastSum, actualSum, forecastSum - actualSum, (System.currentTimeMillis() - begin) / 1000L));
break;
}
}
}

private static String digits(long val, int digits) {
long hi = 1L << (digits * 4);
return toString(hi | (val & (hi - 1)), DIGITS_ARRAY.length).substring(1);
}

private static String toString(long i, int radix) {
if (radix < MIN_RADIX || radix > MAX_RADIX) {
radix = 10;
}
if (radix == 10) {
return Long.toString(i);
}

final int size = 65;
int charPos = 64;
char[] buf = new char[size];
boolean negative = (i < 0);
if (!negative) {
i = -i;
}
while (i <= -radix) {
buf[charPos--] = DIGITS_ARRAY[(int) (-(i % radix))];
i = i / radix;
}
buf[charPos] = DIGITS_ARRAY[(int) (-i)];
if (negative) {
buf[--charPos] = '-';
}

return new String(buf, charPos, (size - charPos));
}
}

image.png

Java11 部分新特性一览

9月26日,Java 11(LTS 长期支持版)如期而至,虽然短期很难投入生产使用(毕竟现在还在用jdk7呢,哈哈哈哈)但是还是非常有必要了解一下相关的特性的。

jshell

使用jshell可以像python交互模式那样直接运行代码


Java8 Stream Api使用详解

jdk8发布至今已有几年有余,是一个影响深远且具有革命意义的版本,目前jdk版本已直奔v11.0, 发展之迅速让人始料未及。本文在假设已有 java8 lambda 语法的基础下,通过几个示例,快速上手Stream 流处理相关的 API 使用。

什么是流操作

流操作就是一条流水线,将元素放在流水线上一个个地进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
List<User> list = Lists.newArrayList(user1,user2);
List<String> resultList =
list.
// 将集合转换成流对象
stream()
// 将List<User>遍历,将每个元素的name取出,组装成新的List<String>
.map(User::getName)
// 按照默认规则排序
.sorted()
// 只取20条数据
.limit(20)
// 收集流数据,组装成最终需要集合(List<String>)
.collect(toList())

在以上的代码中,通过短短的几行代码,行云流水般的完成了一系列操作,这些操作在 jdk7 之前,是远远不能如此简单明了而高效的。

关于Java代码的一些优化技巧

减少重复计算

1
2
3
4
5
6
7
for (int i = 0; i < list.size(); i++) {
// do something;
}
for (int i = 0; int len = list.size(); i < len; i++) {
// do something;
}
// 不要觉得麻烦, 尽量减少变量的重复计算, 这在大量数据集合遍历时非常有效.

不要创建大量的对象引用

1
2
3
4
5
6
7
8
9
for (; ;) {
Object obj = new Object();
}

Object obj = null;
for (; ;) {
obj = new Object();
}
// 能省就省.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×