软件开发阶段
软件详细设计阶段的主要任务包括:对模块内的数据结构进行设计;对数据库进行物理设计;对每个模块进行详细的算法设计;代码设计、输入/输出设计、用户界面设计等其他设计。
软件概要设计阶段的主要任务包括:
(1) 软件系统总体结构设计,将系统划分成模块;确定每个模块的功能;确定模块之间的调用关系;确定模块之间的接口,即模块之间传递的信息;评价模块结构的质量。
(2) 数据结构及数据库设计。
java的概念
JAVA是解释型语言,可以生成中间代码后再边解释为目标代码边执行,也就是即时编译,可以根据运行机器优化代码,采用的是动态优化编译。
JAVA的栈空间只存放基本类型、引用类型变量和方法,而堆中存储实例对象。
编码技巧
如果 try-catch 语句在 finally 语块中进行了 return 操作,那么 catch 语句块中手动抛出的异常也会被覆盖,同样不会自动回滚。
核心线程数设置
推荐的线程数量计算公式有两种:
- 公式一:线程数量=(线程总时间/瓶颈资源时间) X 瓶颈资源的线程并行数。
- 公式二:QPS=1000/线程总时间 X 线程数。
由于用户场景的不同,对于一些复杂的系统,实际上很难计算出最优的线程配置,只能根据测试数据和用户场景,结合公式给出一个相对合理的范围然后对范围内的数据进行性能测试,选择相对最优值。
什么是布隆过滤器
它实际上是由一个很长的二进制数组+一系列hash算法映射函数,用于判断一个元素是否存在于集合中。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
布隆过滤器原理
布隆过滤器其实就是是一个BIT数组,通过一系列hash算法映射出对应的hash,然后将hash对应的数组下标位置改为1。查询时就是对数据在进行一系列hash算法得到下标,从BIT数组里取数据,如果是1 则说明数据有可能存在,如果是0 说明一定不存在。
java实现布隆过滤器
package com.fandf.test.redis;
import java.util.BitSet;
/**
* java布隆过滤器
*
* @author fandongfeng
*/
public class MyBloomFilter {
/**
* 位数组大小
*/
private static final int DEFAULT_SIZE = 2 << 24;
/**
* 通过这个数组创建多个Hash函数
*/
private static final int[] SEEDS = new int[]{4, 8, 16, 32, 64, 128, 256};
/**
* 初始化位数组,数组中的元素只能是 0 或者 1
*/
private final BitSet bits = new BitSet(DEFAULT_SIZE);
/**
* Hash函数数组
*/
private final MyHash[] myHashes = new MyHash[SEEDS.length];
/**
* 初始化多个包含 Hash 函数的类数组,每个类中的 Hash 函数都不一样
*/
public MyBloomFilter() {
// 初始化多个不同的 Hash 函数
for (int i = 0; i < SEEDS.length; i++) {
myHashes[i] = new MyHash(DEFAULT_SIZE, SEEDS[i]);
}
}
/**
* 添加元素到位数组
*/
public void add(Object value) {
for (MyHash myHash : myHashes) {
bits.set(myHash.hash(value), true);
}
}
/**
* 判断指定元素是否存在于位数组
*/
public boolean contains(Object value) {
boolean result = true;
for (MyHash myHash : myHashes) {
result = result && bits.get(myHash.hash(value));
}
return result;
}
/**
* 自定义 Hash 函数
*/
private class MyHash {
private int cap;
private int seed;
MyHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
/**
* 计算 Hash 值
*/
int hash(Object obj) {
return (obj == null) ? 0 : Math.abs(seed * (cap - 1) & (obj.hashCode() ^ (obj.hashCode() >>> 16)));
}
}
public static void main(String[] args) {
String str = "好好学技术";
MyBloomFilter myBloomFilter = new MyBloomFilter();
System.out.println("str是否存在:" + myBloomFilter.contains(str));
myBloomFilter.add(str);
System.out.println("str是否存在:" + myBloomFilter.contains(str));
}
}
Guava实现布隆过滤器
引入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
package com.fandf.test.redis;
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
/**
* @author fandongfeng
*/
public class GuavaBloomFilter {
public static void main(String[] args) {
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),100000,0.01);
bloomFilter.put("好好学技术");
System.out.println(bloomFilter.mightContain("不好好学技术"));
System.out.println(bloomFilter.mightContain("好好学技术"));
}
}
hutool实现布隆过滤器
引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.3</version>
</dependency>
package com.fandf.test.redis;
import cn.hutool.bloomfilter.BitMapBloomFilter;
import cn.hutool.bloomfilter.BloomFilterUtil;
/**
* @author fandongfeng
*/
public class HutoolBloomFilter {
public static void main(String[] args) {
BitMapBloomFilter bloomFilter = BloomFilterUtil.createBitMap(1000);
bloomFilter.add("好好学技术");
System.out.println(bloomFilter.contains("不好好学技术"));
System.out.println(bloomFilter.contains("好好学技术"));
}
}
双冒号“::”就是Java中的方法引用
方法引用的格式是类名::方法名。一般是用作Lambda表达式。
形如 ClassName::methodName 或者 objectName::methodName 的表达式,叫做方法引用(Method Reference)。看看编译器是如何根据 “晦涩难懂” 的 Method Reference 来推断开发者的意图的。例如:
1.表达式:
person -> person.getName();
可以替换成:
Person::getName
2.表达式:
() -> new HashMap<>();
可以替换成:
HashMap::new
四种方法引用
- 指向静态方法的引用
- 指向某个对象的实例方法的引用
- 指向某个类型的实例方法的引用
- 指向构造方法的引用
JVM参数
-
Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。
-
Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。
-
Xss 是指设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等。
jvm参数调优的一般原则
-
减少gc,让老年代的空间大于新生代。因为gc回收很耗性能
-
让堆的初始值和最大值一致,减少gc回收次数
Java应用线上问题排查思路:定位出问题的线程
一、传统方法
- top定位CPU最高的进程
执行top命令 - 定位使用CPU最高的线程
执行 top -Hp pid - 线程id转化16进制
执行 printf '0x%x’tid - 找到线程堆栈
执行 jstack pid | grep tid
二、show-busy-java-threads
这个脚本来自于github上一个开源项目,项目提供了很多有用的脚本,show-busy-java-threads就是其中的一个。使用这个脚本,可以直接简化方法A中的繁琐步骤。如下:
执行
./show-busy-java-threads
# 从所有运行的Java进程中找出最消耗CPU的线程(缺省5个),打印出其线程栈。
可以手动指定要分析的Java进程Id,以保证只会显示你关心的那个Java进程的信息:
show-busy-java-threads -p <指定的Java进程Id>
show-busy-java-threads -c <要显示的线程栈数>
jvm内存溢出的原因
通常是程序中的代码陷入了死循环或者是程序中产生了大量的大对象未及时回收,导致在多次GC后,内存资源依然紧张。
dump生成
dump可以是内存溢出时让其自动生成,或者手工直接导。
(一)配置jvm参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/biapp/m.hprof
(二)手工直接导
jmap -dump:live,format=b,file=m.hprof PID
jvm内存堆情况查看命令用法
一、jmap命令
(一) jmap命令的作用
jmap命令是一个可以输出所有内存中对象的工具,甚至可以将VM中的heap,以二进制输出成文本。
打印出某个java进程(使用pid)内存内的,所有‘对象’的情况(如:产生那些对象,及其数量)。
(二) jmap命令的使用
jmap -heap 进程id #查看堆的配置及jvm堆内存各个代的内存使用情况(年轻代、年老代、永久代的内存大小及使用率)
二、jstat命令
(一)jstat命令的作用
查看gc实时执行情况
(二)命令的使用
jstat -gc 30149 5000 #每5秒一次显示进程号为30149的 java进程的GC情况
jstat -gcutil 30149 5000 #每5秒一次显示进程号为30149的 java进程垃圾回收统计情况
类加载机制
类什么时候被加载/类加载时机?
- 生成该类对象的时候,会加载该类及该类的所有父类;
- 访问该类的静态成员时;
- Class.forName();
- 虚拟机启动时,定义了main()方法的类
java对象实例化有几种方式
- 用new语句创建对象:Student student = new Student();
- 通过工厂方法返回对象;
- 运用反射手段:
c1 = Class.forName('com.ghgj.reflect.Student');
Student student3 = (Student)c1.newInstance();
- 调用对象的clone()方法:
Student student2 = (Student) student.clone();
- 通过I/O流(包括反序列化),如运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法:
ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("C:/student.txt"));
out.writeObject(student);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("C:/student.txt"));
Student student4 = (Student)in.readObject();
in.close();
static修饰的类、变量、方法、代码块
- JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配;
- 与静态类方法一样,静态嵌套类不能直接引用其封闭类中定义的实例变量或方法:它只能通过对象引用来使用它们。