1.0 压力测试
内存泄漏(循环),并发与同步
- 响应时间
- hps: 每秒点击次数
- tps: 系统每秒处理交易次数(事务 完整的场景链)
- qps: 系统每秒处理查询次数,
- 最大响应时间
- 最小响应时间
- 90%响应时间, 排序后90% 内响应时间
- 吞吐量,响应时间,错误率
1.1 JMeter 安装
2.性能监控 堆内存与垃圾回收
cpu密集型和IO密集型
2.1 jvm内存模型
1. 堆(Heap)
堆内存是所有线程共有的,可以分为两个部分:年轻代和老年代。下图中的Perm代表的是永久代,但是注意永久代并不属于堆内存中的一部分,同时jdk1.8之后永久代也将被移除。
堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置(最大最小值都要小于1G),前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小堆内存的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,当然为了避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。堆内存 = 新生代+老生代+持久代。在我们垃圾回收的时候,我们往往将堆内存分成新生代和老生代(大小比例1:2),新生代中由Eden和Survivor0,Survivor1组成,三者的比例是8:1:1,新生代的回收机制采用复制算法,在Minor GC的时候,我们都留一个存活区用来存放存活的对象,真正进行的区域是Eden+其中一个存活区,当我们的对象时长超过一定年龄时(默认15,可以通过参数设置),将会把对象放入老生代,当然大的对象会直接进入老生代。老生代采用的回收算法是标记整理算法。
2. 方法区(Method Area)
方法区也称”永久代“,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB(64位JVM由于指针膨胀,默认是85M),可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。它是一片连续的堆空间,永久代的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。不过,一个明显的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会报OOM的错误。参数是通过-XX:PermSize和-XX:MaxPermSize来设定的。
3.虚拟机栈(JVM Stack)
描述的是java方法执行的内存模型:每个方法被执行的时候都会创建一个”栈帧”,用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。声明周期与线程相同,是线程私有的。栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区被组织为以一个字长为单位、从0开始计数的数组,和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的,可以看作为临时数据的存储区域。除了局部变量区和操作数栈外,java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在java栈帧的帧数据区中。
局部变量表: 存放了编译器可知的各种基本数据类型、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。
4.本地方法栈(Native Stack)
与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。(栈的空间大小远远小于堆)
5.程序计数器(PC Register)
是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。
6.直接内存
直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小.
2.2 Jconsole,Jvisualvm
休眠: sleep, 等待:wait ,驻留:线程池中空闲的 ,监视: 阻塞的线程,等待锁
下载插件:Visual gc
2.3 汇总各个中间件和服务的内存\cpu使用情况
- nginx使用情况
给阿里云服务器中的docker nginx发送请求,
docker stats
- 网关服务,发送localhost:88 端口发送请求,打开jvisualvm 查看cpu,内存情况
- 简单服务 /hello ,直接返回一个"hello"
- 经过网关,发送一个请求,“/hello” 即:网关+简单服务
- 全链路 ,gulimall.com/hello
- 首页一级菜单渲染,index.html显示,localhost:10000/ 经过了数据库查询和thymeleaf渲染
- 三级分类数据获取, “localhost:10000/index/catelog.json”
- 首页全量数据获取 静态css,logo等
2.4 优化
- thymeleaf缓存** ,spring.thymeleaf.cache =true
- logging只打印错误日志, logging.level.com.atguigu.gulimall: error
- 数据库优化: 给查询的字段加上 索引,
- 静态资源,动静分离放到nginx上, 规则:/static/** 所有请求都有nginx直接返回
在服务器上 /mydata/nginx/html/ 下创建一个 static目录,然后将静态资源放进去.将网页中静态资源请求,改到nginx中的/static 下 , 即gulimall.conf vi插入
- 给堆内存扩大: -Xmx1024m -Xmx1024m -Xmn512m
- 业务逻辑优化: 减少数据库查询次数,抽取总数据方法,然后各个方法从这个数据中 分类获取
- 缓存和分布式锁:
3.缓存
- 即时性,数据一致性要求不高的
- 访问量大而且更新频率不高的数据(读多,写少)
3.1 本地缓存和分布式缓存
- 引入springboot的redis依赖,host信息
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
spring
redis:
host: 127.0.0.1
port: 6379
- 使用Springboot自动配置好的StringRedisTemplate 来操作redis
@Autowired
private StringRedisTemplate redisTemplate;
//TODO 产生堆外内存溢出, Outofdirectmemoryerror :
//1.springboot 2.0后默认使用了lettuce 操作客户端,使用netty进行网络通信
//解决方案 -Dio.netty.maxDirectMemory 1.升级lettuce客户端, 2.切换使用jedis
// lettuce ,jedis 操作redis的底层客户端, spring 又封装了这二者为 redistemplate
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
// 给缓存中json字符串,拿出的json字符串,还要逆转为能用的对象类型 [序列化与反序列]
// 1. 加入缓存逻辑(缓存中存入的数据是 json字符串,因为json是跨语言的 兼容性好)
String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
if (StringUtils.isEmpty(catalogJSON)){
//2.缓存中没有,则查询数据库
Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
//3.将查出数据转为json字符串,,然后放入缓存中
String s = JSON.toJSONString(catalogJsonFromDb);
redisTemplate.opsForValue().set("catalogJSON",s);
return catalogJsonFromDb;
}