Earyant 的技术博客

欢迎来到 Earyant 的技术博客,在这里我将与你分享新技术。

  • [ ] mysql 主从配置
  • [ ] mybatis 自动生成代码
  • [ ] 读写分离,与主从配置相关联
  • [ ] 分布式 redis 缓存
  • [ ] tomcat session 共享、session 绑定
  • [ ] 分布式锁:redis、mysql

  • 什么是类的加载

    类的加载指的是将类的.class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class 对象,Class 对象封装了类在方法区内的数据结构,并且向 Java 程序员提供了访问方法区内的数据结构的接口。

  • 类的生命周期

    • 加载,查找并加载类的二进制数据,在 Java 堆中也创建一个 java.lang.Class 类的对象
    • 连接,连接又包含三块内容:验证、准备、初始化。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用
    • 初始化,为类的静态变量赋予正确的初始值
    • 使用,new 出对象程序中使用
    • 卸载,执行垃圾回收
  • 类加载器

    • 启动类加载器:Bootstrap ClassLoader,负责加载存放在 JDK\jre\lib (JDK 代表 JDK 的安装目录,下同) 下,或被 - Xbootclasspath 参数指定的路径中的,并且能被虚拟机识别的类库

    • 扩展类加载器:Extension ClassLoader,该加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载 DK\jre\lib\ext 目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如 javax.* 开头的类),开发者可以直接使用扩展类加载器。

    • 应用程序类加载器:Application ClassLoader,该类加载器由 sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器

  • 双亲委派模型

    \-  全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
    
    - 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
    
    - 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效
    

方法区和堆是所有线程共享的内存区域;而 java 栈、本地方法栈和程序计数器是运行是线程私有的内存区域。

  • Java 堆(Heap), 是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

  • 方法区(Method Area), 方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

  • 程序计数器(Program Counter Register), 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。

  • JVM 栈(JVM Stacks), 与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

  • 本地方法栈(Native Method Stacks), 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。

对象分配规则

  • 对象优先分配在 Eden 区,如果 Eden 区没有足够的空间时,虚拟机执行一次 Minor GC。

  • 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在 Eden 区和两个 Survivor 区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。

  • 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了 1 次 Minor GC 那么对象会进入 Survivor 区,之后每经过一次 Minor GC 那么对象的年龄加 1,知道达到阀值对象进入老年区。

  • 动态判断对象的年龄。如果 Survivor 区中相同年龄的所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。

  • 空间分配担保。每次进行 Minor GC 时,JVM 会计算 Survivor 区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次 Full GC,如果小于检查 HandlePromotionFailure 设置,如果 true 则只进行 Monitor GC, 如果 false 则进行 Full GC。

对象存活判断

判断对象是否存活一般有两种方式:

  • 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加 1,引用释放时计数减 1,计数为 0 时可以回收。此方法简单,无法解决对象相互循环引用的问题。

  • 可达性分析(Reachability Analysis):从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

GC 算法

  • GC 最基础的算法有三种:标记 - 清除算法、复制算法、标记 - 压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。

  • 标记 - 清除算法,“标记 - 清除”(Mark-Sweep)算法,如它的名字一样,算法分为 “标记” 和 “清除” 两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

  • 复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

  • 标记 - 压缩算法,标记过程仍然与 “标记 - 清除” 算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

  • 分代收集算法,“分代收集”(Generational Collection)算法,把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

垃圾回收器

  • Serial 收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。

  • ParNew 收集器,ParNew 收集器其实就是 Serial 收集器的多线程版本。

  • Parallel 收集器,Parallel Scavenge 收集器类似 ParNew 收集器,Parallel 收集器更关注系统的吞吐量。

  • Parallel Old 收集器,Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和 “标记-整理” 算法

  • CMS 收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。

  • G1 收集器,G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征

在 Java 语言中,GC Roots 包括:

  • 虚拟机栈中引用的对象。
  • 方法区中类静态属性实体引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中 JNI 引用的对象。

调优命令

Sun JDK 监控和故障处理命令有 jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool, 显示指定系统内所有的 HotSpot 虚拟机进程。
  • jstat,JVM statistics Monitoring 是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。
  • jmap,JVM Memory Map 命令用于生成 heap dump 文件
  • jhat,JVM Heap Analysis Tool 命令是与 jmap 搭配使用,用来分析 jmap 生成的 dump,jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 的分析结果后,可以在浏览器中查看
  • jstack,用于生成 java 虚拟机当前时刻的线程快照。
  • jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。

主要关注点:

  • GC 日志分析

  • 调优命令

  • 调优工具

参考
参考

##jvm 系列 (七):jvm 调优 - 工具篇

调优工具

  • 常用调优工具分为两类,jdk 自带监控工具:jconsole 和 jvisualvm,第三方有:MAT (Memory Analyzer Tool)、GChisto。
  • jconsole,Java Monitoring and Management Console 是从 java5 开始,在 JDK 中自带的 java 监控和管理控制台,用于对 JVM 中内存,线程和类等的监控
  • jvisualvm,jdk 自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC 变化等。
  • MAT,Memory Analyzer Tool,一个基于 Eclipse 的内存分析工具,是一个快速、功能丰富的 Java heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
  • GChisto,一款专业分析 gc 日志的工具

sed 编辑器

不修改原内容的行级别流编辑器

  • sed ‘s/baidu /earyant/‘ access.log | head -10

    将日志文件中的 baidu 替换成 earyant 输出
    s 表示执行的事文本替换命令

  • sed -n ‘2,6p’ access.log

    -n 表示只输出指定的行,’2,6p’表示选择的事第二行到第六行

  • sed ‘/earyant/d’ access.log

    d 表示执行文本删除命令,将包含 earyant 的行删除

  • sed ‘=’ access.log

    显示文本行号

  • sed -e ‘i\head’ access.log | head -10

    在行首插入文本

  • sed -e ‘a\end’ access.log | head -10

    在文末追加文本

  • sed -e ‘/baidu/c\hello’ access.log | head -10

    c 命令对文本进行替换,查找 /baidu/ 匹配的行,用 hello 对匹配的行进行替换,与 s 不同的是,这个替换行,s 是替换单词

  • sed -n ‘1,5p;1,5=’ access.log

    多条命令,分号隔开

awk

  • awk ‘{print $1}’ access.log | head -10

    打印指定的列

  • awk ‘/baidu/{print $5,%6}’ access.log | head -10

    筛选指定的行,并且打印出其中一部分列

  • awk ‘length{$0} > 40 {print $3}’ access.log | head -10

    $0 表示当前的行,length ($0) 获取当前行的长度,print $3 打印出第三列

  • awk ‘{line = sprintf (“method:%s,response:%s”,$3,$7); print line}’ access.log | head -10

    定义 line 接收 sprintf 输出,sprintf 用户格式化输出第三行的请求式和第七行的响应时间

shell

  • 系统 load 超过 2 或者磁盘利用率超过 85% 报警:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
#earyantlee@gmail.com

#top取系统load值, -n 1 表示只刷新一次
# sed 过滤第一行
# top命令会输出1分钟、5分钟、15分钟load的平均值,awk筛选出1分钟内的平均load,赋值给load
load = `top -n 1| sed -n '1p' | awk '{print $11}'`
# 从右边开始,过滤掉不需要的逗号
load = ${load%\,*}
# df取得磁盘利用率信息,用sed筛选磁盘总利用率第二行
disk_usage = `df -h | sed -n '2p' |awk '{print $(NF - 1)}' `
# 过滤掉百分号
disk_usage = ${disk_usage%\%*}
overhead = `expr $load \> 2.00`
if [$overhead -eq 1];then
echo "System load is overhead"
fi
if [$disk_usage -gt 85 ]; then
echo "disk is nearly full ,need more disk space"
fi
exit 0
  • 读取日志文件,对字段切割,插入到 sql。
    db:
1
2
3
4
5
6
7
8
9
create table access_log(
ip varchar(20),# ip地址
rt bigint,# 响应时间
method varchar(10), #请求方式
url varchar (400), # 请求地址
refer varchar(400),# 请求来源
return_code int,# 返回码
response_size bigint # 响应大小
);

shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
#earyantlee@gmail.com
# 日志路径
ACCESS_FILE="D:\\0\\nginx-1.11.13\\logs\\access2.log"
while read LINE
do
# 系统数组分隔符
OLD_IFS="$IFS"
IFS=" "
# 每行分割成数组
field_arr=($LINE)
IFS="$OLD_IFS"
# 生成插入语句,各列的值对应什么,懒得改了。
STATEMENT="insert into access_log values('${field_arr[0]}' , '${field_arr[1]}', '${field_arr[2]}', '${field_arr[3]}', '${field_arr[4]}', '${field_arr[5]}', '${field_arr[6]}');"
echo $STATEMENT
mysql earyant -u root -p123456 -e "${STATEMENT}"
done < $ACCESS_FILE
exit 0

查看文件的内容

  • cat:正序查看

    cat access.log

  • tac:倒序查看

    tac access.log

分页显示文件

  • more

    more access.log

    Enter 显示文件下一行
    空格显示下一页
    F 显示下一屏内容
    B 显示上一屏内容

  • less

    /GET 查找 GET 字符串

显示文件尾

  • tail

    tail -f -n 500 access.log

    -f 持续查看
    -n 显示最后 n 行

显示文件头

  • head

    head -n2 access.log

内容排序

  • sort

    sort -n -r access.log

    -n 按照数字排序
    -r 逆序排序

    sort -k 2 -t “ “ -n access.log

    -k 指定排序的列
    -t 指定列分隔符
    -n 按照数字排序

字符统计

  • wc

    wc -l access.log

    -l 统计文件中的行数

    wc -c access.log

    -c 显示文件的字节数

    wc -L access.log

    -L 得出最长的行长度

    wc -w access.log

    -w 查看文件包含多少单词

重复行

  • uniq

    sort uniqfile| uniq -c

    -c 用来在每一行前面加上该行出现的次数

    sort uniqfile | uniq -c -u

    -u 只会显示出现一次的行

    sort uniqfile | uniq -c -d

    -d 只会显示重复出现的行

字符串查找

  • grep

    >   grep earyant access.log
    
      earyant 为指定的查找串
    
    > grep -c earyant access.log
    
      -c 可以显示查找到的行数
    
    >   grep 'G.\*T' access.log
    
      支持正则表达式
    

    文件查找

  • find

    find /home/earyant -name access.log

    在 /home/earyant 目录下查找文件名为 access.log 的文件

    find /home/earyant -name “*.txt”

    find . -print

    打印当前目录所有文件

  • whereis

    whereis java

  • which

    which java

表达式求值

  • expr

    expr 10 * 3
    expr 10 % 3
    expr index “earyant.github.io” earyant

压缩

  • tar

    tar -cf aaa.tar tmp1 tmp2

    将当前目录下的tmp1和tmp2目录打包成aaa.tar
    -c 表示生成新包
    -f 指定包名称
    

    tar -tf aaa.tar

    -t 能够列出包中文件的名称
    

    tar -xf aaa.tar

    -x 将打包文件解压
    

url 访问工具

  • curl

    curl www.baidu.com

    curl -i www.baidu.com

    -i 返回带 header 的文档

    curl -I www.baidu.com

    -I 只返回页面的 header 信息

    查看请求访问量

cat access.log | cut -f1 -d “ “ | sort | uniq -c | sort -k 1 -n -r | head -10

访问量排名前 10 的 ip 地址

cat access.log | cut -f4 -d “ “ | sort | uniq -c | sort -k 1 -n -r | head -10

页面访问量排名前 10 的 url

查看最耗时的页面

cat access.log | sort -k 2 -n -r |head -10

统计 404 请求的占比

1
export total_line = `wc -l access.log | cut -f1 -d " "` && export not_found_line = `awk '$6=='404'{print $6}' access.log | wc -l`  && expr $ not_found_line \*100 / $total_line

1.1 - 使用和避免 null

map 允许 null 作为键,但只能有一个。
concurrentHashMap 不允许 null 作为键。

Optional

1
2
3
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

静态创建方法

  • of
  • ofNullable
  • empty

    实例方法

  • isPresent
  • get
  • or (jdk 中是 orElse)
  • orNull
  • asSet

意义:
使用 Optional 除了赋予 null 语义,增加了可读性,最大的优点在于它是一种傻瓜式的防护。Optional 迫使你积极思考引用缺失的情况,因为你必须显式地从 Optional 获取引用。直接使用 null 很容易让人忘掉某些情形,尽管 FindBugs 可以帮助查找 null 相关的问题,但是我们还是认为它并不能准确地定位问题根源。
如同输入参数,方法的返回值也可能是 null。和其他人一样,你绝对很可能会忘记别人写的方法 method (a,b) 会返回一个 null,就好像当你实现 method (a,b) 时,也很可能忘记输入参数 a 可以为 null。将方法的返回类型指定为 Optional,也可以迫使调用者思考返回的引用缺失的情形。

使用方式:

  • 错误的使用方式:
    1
    2
    3
    4
    5
    6
    Optional<User> user = Optional.ofNullable(user);
    if (user.isPresent()) {
    int sex = user.getSex();
    // 链式调用,最容易出现空指针
    int age = user.getParent().getParent().getParent().getAge();
    }
  • 正确的使用方式
    1
    2
    3
    Optional<User> user = Optional.ofNullable(user);
    int sex = user.map(User::getSex).orElse(0);
    int age = user.map(User::getParent).map(User::getParent).map(User::getParent).map(User::getAge).orElse(0);

其他处理 null 的便利方法

  • Objects.firstNonNull(T, T)
  • Objects 还有其它一些方法专门处理 null 或空字符串:emptyToNull (String),nullToEmpty (String),isNullOrEmpty (String)。

1.2 - 前置条件 Preconditions

方法声明(不包括额外参数) 描述 检查失败时抛出的异常
checkArgument(boolean) 检查 boolean 是否为 true,用来检查传递给方法的参数。 IllegalArgumentException
checkNotNull(T) 检查 value 是否为 null,该方法直接返回 value,因此可以内嵌使用 checkNotNull。 NullPointerException
checkState(boolean) 用来检查对象的某些状态。 IllegalStateException
checkElementIndex(int index, int size) 检查 index 作为索引值对某个列表、字符串或数组是否有效 IndexOutOfBoundsException
checkPositionIndex(int index, int size) 检查 index 作为位置值对某个列表、字符串或数组是否有效。 IndexOutOfBoundsException
checkPositionIndexes(int start, int end, int size) 检查 [start, end] 表示的位置范围对某个列表、字符串或数组是否有效 * IndexOutOfBoundsException

参考

如果我孑身一人是个赛车手
无牵无挂,无畏无惧
即使受伤、死亡
也会为我爱的事倾尽所有

如果我生在乱世是个将军
交出我爱的人换取一时和平
不要和我讲大义
提枪上马,马革裹尸不休

最甜的是西瓜中间的那口

可我连籽都吃掉

我爱的东西本来就不多

爱就爱它的所有

本文记录 docker 搭建 elk,并简单结合 logstash 日志输出

filebeat

docker pull docker.elastic.co/beats/filebeat:6.2.1

  • filebeat.yml
    enabled 要改为 true,filebeat 默认为 false
    1
    2
    3
    4
    5
    6
    7
    8
    filebeat.prospectors:
    - type: log
    enabled: true
    paths:
    - D:\workspace\baidu\baiyi\baidu\dsp\dsp-main-server\log\*.log
    output:
    logstash:
    hosts: ["192.168.99.100:5044"]

    docker run -it —name filebeat -v /data/filebeat.yml::/usr/share/filebeat/filebeat.yml

logstash

改写 /opt/logstash/logstash/config/logstash.yml 文件

  • logstash.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
input{
beats {
port => 5044
}
}
output{
stdout{ codec => rubydebug }
elasticsearch {
hosts => "192.168.99.100:9200"
index => "t-server-%{+YYYY.MM.dd}"
document_type => "log4j_type"
}
}