沧海一粟

天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。

0%

1
2
java -XX:+PrintFlagsFinal -version | grep HeapSize  # 查看堆内存配置的默认值
jmap -heap pid

没有万能的JVM参数配置,如果有的话就可能是内存给大点,代码写好点,其它用默认 (JVM已经优化了很多参数)

常用配置

2C4G容器常见配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-server
-Xcomp
-Xmx2g
-Xms2g
-Xmn800m
-XX:MetaspaceSize=512m
-XX:MaxMetaspaceSize=512M
-Xss256k
-XX:+DisableExplicitGC
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:LargePageSizeInBytes=128m
-XX:+UseFastAccessorMethods
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=75
-XX:+HeapDumpOnOutOfMemoryError
-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

2C2G激进配置

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
-Dfile.encoding=UTF-8
-Xcomp
-Xmx1400m
-Xms1400m
-Xmn700m
-XX:SurvivorRatio=3
-XX:NewRatio=1
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=128m
-Xss256k
-XX:+DisableExplicitGC
-XX:+UseConcMarkSweepGC
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:LargePageSizeInBytes=128m
-XX:+UseFastAccessorMethods
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=75
-XX:+HeapDumpOnOutOfMemoryError
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintTenuringDistribution
-Xloggc:/Users/weikeqin1/jvm_gc.log
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining
-XX:+PrintCompilation

常用的JVM配置

-client The client VM is tuned for reducing start-up time and memory footprint. Invoke it by using the -client JVM command-line option.
-server The server VM is designed for maximum program execution speed. Invoke it by using the -server JVM command-line option.

-Dfile.encoding=UTF-8 文件使用UTF-8编码
-Xms2g = -XX:InitialHeapSize=2g = -XX:MinHeapSize=2g 设置初始堆大小为2G
-Xmx2g = -XX:MaxHeapSize=2g 设置最大堆大小为2G
-Xmn1g 设置新生代大小为1G

-XX:MinHeapFreeRatio=
-XX:MaxHeapFreeRatio=
-XX:NewRatio
-XX:NewSize
-XX:MaxNewSize
-XX:+AggressiveHeap

-Xss256k 设置每个线程的堆栈大小

-XX:LargePageSizeInBytes=128m 内存页的大小不可设置过大, 会影响Perm的大小
-XX:MaxDirectMemorySize=536870912

-XX:+UseFastAccessorMethods 原始类型的快速优化

-XX:PetenureSizeThreshold= 设置直接被分配到老年代的最大阀值

-XX:+TraceClassLoading 打印类加载信息

配置垃圾回收算法

-XX:+UseG1GC 使用G1垃圾收集器

gc配置

-XX:+PrintGC 用于垃圾收集时的信息打印 -verbosegc (which is equivalent to -XX:+PrintGC) sets the detail level of the log to fine.
-XX:+PrintGCDetails 打印GC详细信息 ets the detail level to finer.
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式 245469.1 )
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+HeapDumpBeforeFullGC FullGC前保存堆栈信息 测试环境使用,线上最好别用,小心磁盘被打满
-XX:+HeapDumpAfterFullGC FullGC后保存堆栈信息 测试环境使用,线上最好别用,小心磁盘被打满

-XX:+DisableExplicitGC 禁止代码中显示调用GC
-Xloggc:filename gc日志保存到指定文件 把filename替换成 /home/admin/gc.log

-XX:+HeapDumpOnOutOfMemoryError OOM后保存堆栈信息
-XX:-OmitStackTraceInFastThrow 当大量抛出同样的异常的后,后面的异常输出将不打印堆栈
-XX:ErrorFile=logs/hs_err_pid%p.log

JIT config

JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM会尝试将其编译为机器码,从而提高执行速度。

在 Java8 之前,HotSpot 集成了两个 JIT,用 C1 和 C2 来完成 JVM 中的即时编译。
到了 Java9,AOT 编译器被引入。AOT 是在程序运行前进行的静态编译,这样就可以避免运行时的编译消耗和内存消耗,且 .class 文件通过 AOT 编译器是可以编译成 .so 的二进制文件的。
Java10,一个新的 JIT 编译器 Graal 被引入。Graal 是一个以 Java 为主要编程语言、面向 Java bytecode 的编译器。与用 C++ 实现的 C1 和 C2 相比,它的模块化更加明显,也更容易维护。Graal 既可以作为动态编译器,在运行时编译热点方法;也可以作为静态编译器,实现 AOT 编译。

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
-xx:+TieredCompilation  设置分层编译
-cient 使用G1编译器
-server 使用G2编译器
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler 启用Graal即时编译器

–XX:+PrintCompilation 设置JVM打印出刚编译过的相关信息
-XX:CompileCommand='print,*ClassName.methodName'


–XX:ReservedCodeCacheSize= 设置代码缓存大小
-XX:InitialCodeCacheSize= 设置初始代码缓存大小

-XX:CompileThreshold=1024 关闭分层编译的情况下,方法调用计数器的默认阈值,在 C1 模式下是 1500 次,在 C2 模式在是 10000 次

-XX:TieredStopAtLevel=1 在打开分层编译的情况下,Java 虚拟机会在解释执行之后直接由 1 层的 C1 进行编译。
-XX:TieredStopAtLevel=4 在打开分层编译的情况下,Java 虚拟机会在解释执行之后直接由 4 层的 C2 进行编译。


# C2编译器配置
#-XX:Tier4MinInvocationThreshold=200000
#-XX:Tier4InvocationThreshold=300000
#-XX:Tier4CompileThreshold=400000

-XX:MaxInlineLevel=3


-XX:+CICompilerCountPerCPU 设置编译线程的总数目根据处理器数量来动态调整,默认是true,强制设定总编译线程数目时该参数失效
-XX:CICompilerCount=3 设置编译器线程的数量
-Xbatch 设置JIT编译器使用主线程 (日志输出是有序的)

在编译过程中遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段,最重要的编译器优化

1
2
#CompileThreshold must be between 0 and 268435455
-XX:CompileThreshold=128

方法调用计数器:用于统计方法被调用的次数
在关闭分层编译的情况下,方法调用计数器的默认阈值在 C1 模式下是 1500 次,在 C2 模式在是 10000 次,可通过 -XX:CompileThreshold 来设定;
而在分层编译的情况下,-XX:CompileThreshold 指定的阈值将失效,此时将会根据当前待编译的方法数以及编译线程数来动态调整。

1
-XX:OnStackReplacePercentage=10000

回边计数器:用于统计一个方法中循环体代码执行的次数,

在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge),该值用于计算是否触发 C1 编译的阈值,
在不开启分层编译的情况下,C1 默认为 13995,C2 默认为 10700,可通过 -XX: OnStackReplacePercentage=N 来设置;
而在分层编译的情况下,-XX: OnStackReplacePercentage 指定的阈值同样会失效,此时将根据当前待编译的方法数以及编译线程数来动态调整。

在一些循环周期比较长的代码段中,当循环达到回边计数器阈值时,JVM 会认为这段是热点代码,JIT 编译器就会将这段代码编译成机器语言并缓存,在该循环时间段内,会直接将执行代码替换,执行缓存的机器语言。

1
-XX:CICompilerCount=2

经常执行的方法,默认情况下,方法体大小小于 325 字节的都会进行内联,我们可以通过 -XX:MaxFreqInlineSize=N 来设置大小值;
不是经常执行的方法,默认情况下,方法大小小于 35 字节才会进行内联,我们也可以通过 -XX:MaxInlineSize=N 来重置大小值

1
2
3
-XX:+PrintCompilation //在控制台打印编译过程信息
-XX:+UnlockDiagnosticVMOptions //解锁对JVM进行诊断的选项参数。默认是关闭的,开启后支持一些特定参数对JVM进行诊断
-XX:+PrintInlining //将内联方法打印出来
阅读全文 »

多线程设计模式是前人解决并发问题的经验总结,当我们试图解决一个并发问题时,首选方案往往是使用匹配的设计模式,这样能避免走弯路。
大家都熟悉设计模式,所以使用设计模式还能提升方案和代码的可理解性。

避免共享的设计模式
Immutability 模式Copy-on-Write 模式线程本地存储模式 本质上都是为了避免共享,只是实现手段不同而已。

多线程版本IF的设计模式
Guarded Suspension 模式Balking 模式 都可以简单地理解为“多线程版本的 if”,但它们的区别在于前者会等待 if 条件变为真,而后者则不需要等待。

三种最简单的分工模式
Thread-Per-Message模式Worker Thread 模式生产者-消费者模式 是三种最简单实用的多线程分工方法

(1) Immutability Pattern 不变性模式

解决并发问题,其实最简单的办法就是让共享变量只有读操作,而没有写操作。

不变性(Immutability)模式。所谓不变性,简单来讲,就是对象一旦被创建之后,状态就不再发生变化。换句话说,就是变量一旦被赋值,就不允许修改了(没有写操作);没有修改操作,也就是保持了不变性。

Java SDK 里很多类都具备不可变性。例如经常用到的 String 和 Long、Integer、Double 等基础类型的包装类都具备不可变性,这些对象的线程安全性都是靠不可变性来保证的。仔细翻看这些类的声明、属性和方法,你会发现它们都严格遵守不可变类的三点要求:类和属性都是 final 的,所有方法均是只读的。

Java所有的基础类型的包装类都不适合做锁,因为它们内部用到了享元模式,这会导致看上去私有的锁,其实是共有的。

使用 Immutability 模式的注意事项在使用 Immutability 模式的时候,需要注意以下两点:

  1. 对象的所有属性都是 final 的,并不能保证不可变性;
  2. 不可变对象也需要正确发布。

在使用 Immutability 模式的时候一定要确认保持不变性的边界在哪里,是否要求属性对象也具备不可变性。
Foo对象是不变的,但是Foo对象的属性是可以变化的

1
2
3
4
5
6
7
8
9
10
class Foo{
int age=0;
int name="abc";
}
final class Bar {
final Foo foo;
void setAge(int a){
foo.age=a;
}
}

阅读全文 »

在Java中,创建对象,仅仅是在 JVM 的堆里分配一块内存,而创建一个线程,需要调用操作系统内核的 API,然后操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。

进程的运行空间一般分为用户态和内核态,用户态空间一般是进程应用运行空间,而内核态空间一般是指应用需要调用系统资源,应用不能再用户态空间直接调用系统资源,需要通过内核态来系统系统资源。

内核线程(Kernel-Level Thread, KLT)是由操作系统内核支持的线程,内核通过调度器对线程进行调度,并负责完成线程的切换。

阅读全文 »

https://docs.oracle.com/javase/tutorial/
https://docs.oracle.com/en/java/javase/index.html

https://blogs.oracle.com/java-platform-group/

javase14文档 https://docs.oracle.com/en/java/javase/14/index.html
https://docs.oracle.com/javacomponents/index.html
https://docs.oracle.com/en/java/javase/index.html
https://docs.oracle.com/en/java/javase/13/
https://docs.oracle.com/en/java/javase/12/
https://docs.oracle.com/en/java/javase/11/
https://docs.oracle.com/javase/10/
https://docs.oracle.com/javase/9/
https://docs.oracle.com/javase/8/
https://docs.oracle.com/javase/7/docs/index.html

https://dzone.com/articles/jdk-14-records-text-blocks-and-more

Java中的锁

偏向锁

偏向锁主要用来优化同一线程多次申请同一个锁的竞争。

在某些情况下,大部分时间是同一个线程竞争锁资源。

例如,在创建一个线程并在线程中执行循环监听的场景下,或单线程操作一个线程安全集合时,同一线程每次都需要获取和释放锁,每次操作都会发生用户态与内核态的切换。
偏向锁的作用就是,当一个线程再次访问这个同步代码或方法时,该线程只需去对象头的 Mark Word 中去判断一下是否有偏向锁指向它的 ID,无需再进入 Monitor 去竞争对象了。

当对象被当做同步锁并有一个线程抢到了锁时,锁标志位还是 01,“是否偏向锁”标志位设置为 1,并且记录抢到锁的线程 ID,表示进入偏向锁状态。一旦出现其它线程竞争锁资源时,偏向锁就会被撤销。偏向锁的撤销需要等待全局安全点,暂停持有该锁的线程,同时检查该线程是否还在执行该方法,如果是,则升级锁,反之则被其它线程抢占。

轻量级锁

轻量级锁适用于线程交替执行同步块的场景,绝大部分的锁在整个同步周期内都不存在长时间的竞争。
轻量级锁还使用了自旋锁来避免线程用户态与内核态的频繁切换,提高了系统性能;

自旋锁

在锁竞争不激烈且锁占用时间非常短的场景下,自旋锁可以提高系统性能。

JDK1.7 开始,自旋锁默认启用,自旋次数由 JVM 设置决定。
CAS 重试操作意味着长时间地占用 CPU。

乐观锁

乐观锁,在操作共享资源时,抱着乐观的态度进行,认为可以成功地完成操作。但实际上,当多个线程同时操作一个共享资源时,只有一个线程会成功,那么失败的线程呢?它们不会像悲观锁一样在操作系统中挂起,而仅仅是返回,并且系统允许失败的线程重试,也允许自动放弃退出操作。
乐观锁相比悲观锁来说,不会带来死锁、饥饿等活性故障问题,线程间的相互影响也远远比悲观锁要小。
乐观锁没有因竞争造成的系统开销,所以在性能上也是更胜一筹。

CAS 乐观锁在高并发写大于读的场景下,大部分线程的原子操作会失败,失败后的线程将会不断重试 CAS 原子操作,这样就会导致大量线程长时间地占用 CPU 资源,给系统带来很大的性能开销。
CAS 乐观锁在平常使用时比较受限,它只能保证单个变量操作的原子性

References

[1]12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法

读写锁,是一个广为使用的通用技术,无论是操作系统、数据库、编程语言、应用 等都有用到

所有的读写锁都遵守以下三条基本原则:

  1. 允许多个线程同时读共享变量;
  2. 只允许一个线程写共享变量;
  3. 如果一个写线程正在执行写操作,此时禁止读线程读共享变量。

读写锁与互斥锁的一个重要区别就是读写锁允许多个线程同时读共享变量,而互斥锁是不允许的,这是读写锁在读多写少场景下性能优于互斥锁的关键。但读写锁的写操作是互斥的,当一个线程在写共享变量的时候,是不允许其他线程执行写操作和读操作。

redis高性能分析

数据类型

redis为了达到高性能,对数据类型做了调整,更好的适用于高并发

redis的源码相关文件如下:
t_hash.c, t_list.c, t_set.c, t_string.c, t_zset.c and t_stream.c contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.

sds.c is the Redis string library, check http://github.com/antirez/sds for more information.

dict.c is an implementation of a non-blocking hash table which rehashes incrementally.

SDS

SDS使用C语言编写。C语言的字符串是用一个以\0结尾的char[]表示。
但是为什么不直接使用C语言字符串。
C语言字符串的缺点:

  1. 获取字符串长度每次需要遍历char[]数组,时间复杂度O(n)
  2. 修改字符串忘记分配内存容易造成缓冲区溢出 (buffer overflow) 。
  3. 修改字符串需要重新分配内存,设计系统调用。

    SDS优点

  4. 获取字符串长度时间复杂度O(1) 。
  5. 通过封装,避免了直接操作C语言字符串忘记分配内存导致的内存泄露。
  6. 减少修改字符串时内存分配次数。

    空间预分配 惰性空间释放

    二进制安全
    SDS的buf保存的是二进制数组

1
2
3
4
5
struct sdshdr {
int len;
int free;
char buf[];
};

redis里的key是保存了字符串的SDS
redis里的value

阅读全文 »

在压测的时候看了一下缓存监控,看到监控上的数字震惊了,单分片每秒的出流量172MB。从来没想到过性能可以这么高。(优化前的)
分享一些高性能的知识点吧。

(1) 大多数情况存储的时候推荐使用byte

redis-data-types
redis-value-as-byte-vs-plain-string
jvm-serializers

string类型 redis底层存储的都是二进制,所以redis是二进制安全的(binary safe)
看代码的话你会发现redis存储的时候用的都是 byte[]
Redis Strings are binary safe, this means that a Redis string can contain any kind of data, for instance a JPEG image or a serialized Ruby object.

对于存取对象,大多数情况建议使用 set(final byte[] key, final byte[] value) get(final byte[] key)
对于直接存储字符串的,建议直接使用 set(final String key, final String value) get(final String key)

(2) 使用mset mget

mset
mget

(3) 批量处理时优化成一次请求

批处理时可以使用 Pipeline 一次处理,减少网络开销

阅读全文 »

Redis is not a plain key-value store, it is actually a data structures server, supporting different kinds of values. What this means is that, while in traditional key-value stores you associate string keys to string values, in Redis the value is not limited to a simple string, but can also hold more complex data structures. The following is the list of all the data structures supported by Redis, which will be covered separately in this tutorial:

redis data types

Binary-safe strings.
Lists: collections of string elements sorted according to the order of insertion. They are basically linked lists.
Sets: collections of unique, unsorted string elements.
Sorted sets, similar to Sets but where every string element is associated to a floating number value, called score. The elements are always taken sorted by their score, so unlike Sets it is possible to retrieve a range of elements (for example you may ask: give me the top 10, or the bottom 10).
Hashes, which are maps composed of fields associated with values. Both the field and the value are strings. This is very similar to Ruby or Python hashes.
Bit arrays (or simply bitmaps): it is possible, using special commands, to handle String values like an array of bits: you can set and clear individual bits, count all the bits set to 1, find the first set or unset bit, and so forth.
HyperLogLogs: this is a probabilistic data structure which is used in order to estimate the cardinality of a set. Don’t be scared, it is simpler than it seems… See later in the HyperLogLog section of this tutorial.
Streams: append-only collections of map-like entries that provide an abstract log data type. They are covered in depth in the Introduction to Redis Streams.

Redis keys

Redis的key可以是任意类型,可以是字符串,还可以是图片,还可以是空字符串
Redis的key是二进制安全的,底层存储用的byte[]
建议key不要太大
建议key不要太小,太小会比较省内存,但是可读性、可维护性较差。
尝试坚持一个模式。 建议使用 object-type:id,比如 user:1000
redis允许最大的key大小为 512MB

阅读全文 »

elasticsearch 配置笔记

Elasticsearch has three configuration files:

1
2
3
elasticsearch.yml for configuring Elasticsearch
jvm.options for configuring Elasticsearch JVM settings
log4j2.properties for configuring Elasticsearch logging

ES Server 配置

ES 配置

1
2
#自动创建索引
action.auto_create_index: .monitoring*,.watches,.triggered_watches,.watcher-history*,.ml*

可重新加载配置
Just like the settings values in elasticsearch.yml, changes to the keystore contents are not automatically applied to the running Elasticsearch node. Re-reading settings requires a node restart. However, certain secure settings are marked as reloadable.

1
2
POST _nodes/reload_secure_settings
POST _nodes/<node_id>/reload_secure_settings
1
2
3
4
5
curl -X POST "localhost:9200/_nodes/reload_secure_settings?pretty" -H 'Content-Type: application/json' -d'
{
"secure_settings_password": "s3cr3t"
}
'
阅读全文 »