谷粒商城:性能压测JVM堆区
目录
Kit
Apache JMeter
VisualVM
堆内存
jvm内存模型
垃圾回收(Garbage Collection, GC)
新对象分配内存
GC步骤
MinorGC
性能优化
影响因素
优化
nginx动静分离
优化三级分类获取
Jvm参数配置堆区
测试
Kit
Apache JMeter
压力测试,模拟大量用户并发访问
VisualVM
监控、分析和调优 Java 应用程序的性能
VisualGC 插件:监控垃圾回收
堆内存
jvm内存模型
堆区:
所有对象实例和数组都存储在堆区,堆区是线程共享的,所有线程都可以访问堆中的对象。
堆区结构:
-
年轻代(Young Generation):
-
Eden 区:新创建的对象首先分配在这里。
-
Survivor 区:分为 From 和 To 区,存放经过一次 GC 后仍然存活的对象。
-
-
老年代(Old Generation):存放长期存活的对象。
-
元空间(Metaspace):存放类的元数据(如类定义、方法信息等)。
垃圾回收(Garbage Collection, GC)
新对象分配内存
- 新创建的对象首先分配在 Eden 区
- 当 Eden 区满时,触发 Minor GC
- MinorGC后,若Eden区放得下,则存入Eden区
- 若MinorGC后,Eden空间仍然不足,则为大对象,直接分配至Old区
- 若Old区空间不足,触发FullGC,对整个堆内存进行垃圾回收。
GC步骤
(1) 标记(Marking)
-
遍历所有对象,标记哪些对象是存活的(被引用的),哪些是垃圾(未被引用)。
-
从 GC Roots(如线程栈、静态变量等)开始,递归标记所有可达对象。
(2) 清除(Sweeping)
-
清除未被标记的垃圾对象,释放它们占用的内存空间。
-
清除后,内存可能会产生碎片。
(3) 压缩(Compacting)
-
将存活的对象移动到内存的一端,整理出连续的内存空间,减少内存碎片。
-
这一步不是所有 GC 算法都会执行(如 CMS 就不压缩)。
MinorGC
- 标记 Eden 区和 From 区(当前活动的 Survivor 区)中的存活对象。
- 将存活的对象复制到 To 区(另一个 Survivor 区)。
- 清空 Eden 区和 From 区,From和To角色互换
角色交换:
在下次 Minor GC 时,原来的 To 区 变为新的 From 区,而原来的 From 区 变为新的 To 区。
这种交换确保了每次 Minor GC 都有一个空的 Survivor 区用于存放存活对象。
对象晋升:
如果对象在 Survivor 区中经历了多次 Minor GC(默认是 15 次,可以通过
-XX:MaxTenuringThreshold
参数调整),它会被晋升到 老年代。
为什么需要两个 Survivor 区?
减少内存碎片:通过复制算法,将存活对象从一个 Survivor 区复制到另一个 Survivor 区,可以整理内存,减少碎片。
提高垃圾回收效率:每次 Minor GC 只需要处理 Eden 区和其中一个 Survivor 区,而不是整个年轻代。
性能优化
影响因素
中间件
- client - > nginx - > 网关 - > server 中间件越多,网络IO交互越多,性能损耗越大
业务
- DB(MySQl优化)
- 模板渲染(开启缓存)
- 静态资源(动静分离)
优化
1.日志:仅报错
logging:
level:
com.elysia.gulimall.product: error
2. MySQL加索引
3.thtmeleaf 开启ceche
4.nginx动静分离
5.优化业务逻辑
6.通过 JVM 参数(如 -Xmx
和 -Xms
)进行配置堆区
nginx动静分离
将js,css,img等静态资源存放在nginx中,静态资源由nginx返回。
目录:
nginx/html/static/index :css,js,img,json
请求静态资源:
gulimall.com/static/index/js/catalogLoader.js
config:
upstream gulimall {
server 192.168.40.1:88;
}
server {
listen 80;
server_name gulimall.com;
location /static/ {
root /usr/share/nginx/html;
}
location / {
proxy_set_header Host $host;
proxy_pass http://gulimall;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
优化三级分类获取
原代码13行和19行多次查询数据库,获取子分类list
@Override
public List<CategoryEntity> getLevel1Category() {
return this.list(new QueryWrapper<CategoryEntity>().eq("cat_level",1).eq("show_status",1));
}
@Override
public Map<String, List<Level2CategoryVo>> getLevel2AndLevel3Category() {
//一级分类
List<CategoryEntity> level1 = this.getLevel1Category();
Map<String, List<Level2CategoryVo>> categoryMap = level1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(),v ->{
//查询该一级分类下的二级分类
List<CategoryEntity> level2List = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
List<Level2CategoryVo> level2CategoryVos = null;
if (level2List != null){
level2CategoryVos = level2List.stream().map(level2 -> {
//查询 二级分类 下的 三级分类
List<CategoryEntity> level3List = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", level2.getCatId()));
List<Level2CategoryVo.Level3Category> collect = null;
if (level3List != null) {
collect = level3List.stream().map(level3 -> {
Level2CategoryVo.Level3Category level3Category = new Level2CategoryVo.Level3Category(level2.getCatId(), level3.getCatId(), level3.getName());
return level3Category;
}).collect(Collectors.toList());
}
Level2CategoryVo level2CategoryVo = new Level2CategoryVo(v.getCatId(), collect, level2.getCatId(), level2.getName());
return level2CategoryVo;
}).collect(Collectors.toList());
}
return level2CategoryVos;
}));
return categoryMap;
}
获取全部分类,通过filter获取某分类的子分类list
@Override
public Map<String, List<Level2CategoryVo>> getLevel2AndLevel3Category() {
List<CategoryEntity> selectList = this.baseMapper.selectList(null);
//一级分类
List<CategoryEntity> level1 = this.getByParentCid(selectList,0L);
Map<String, List<Level2CategoryVo>> categoryMap = level1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(),v ->{
//查询该一级分类下的二级分类
List<CategoryEntity> level2List = getByParentCid(selectList,v.getCatId());
List<Level2CategoryVo> level2CategoryVos = null;
if (level2List != null){
level2CategoryVos = level2List.stream().map(level2 -> {
//查询 二级分类 下的 三级分类
List<CategoryEntity> level3List = getByParentCid(selectList,level2.getCatId());
List<Level2CategoryVo.Level3Category> collect = null;
if (level3List != null) {
collect = level3List.stream().map(level3 -> {
Level2CategoryVo.Level3Category level3Category = new Level2CategoryVo.Level3Category(level2.getCatId(), level3.getCatId(), level3.getName());
return level3Category;
}).collect(Collectors.toList());
}
Level2CategoryVo level2CategoryVo = new Level2CategoryVo(v.getCatId(), collect, level2.getCatId(), level2.getName());
return level2CategoryVo;
}).collect(Collectors.toList());
}
return level2CategoryVos;
}));
return categoryMap;
}
private List<CategoryEntity> getByParentCid(List<CategoryEntity> selectList,Long parentCid) {
return selectList.stream().filter(categoryEntity -> {
return categoryEntity.getParentCid().equals(parentCid);
} ).collect(Collectors.toList());
}
Jvm参数配置堆区
将堆区固定为1024m,Eden区 512m
测试
全链路测试,请求首页
50线程数,吞吐量:1100/s