淘客那些网站怎么做的,郑州网站开发公司电话,建筑行业的公司有哪些,做网站的文件文章目录 内存调优内存溢出和内存泄漏内存泄露带来什么问题内存泄露案例演示内存泄漏的常见场景场景一场景二 解决内存溢出的方法常用内存监控工具Top命令优缺点 VisualVM软件、插件优缺点监控本地Java进程监控服务器的Java进程#xff08;生产环境不推荐使用#xff09; Art… 文章目录 内存调优内存溢出和内存泄漏内存泄露带来什么问题内存泄露案例演示内存泄漏的常见场景场景一场景二 解决内存溢出的方法常用内存监控工具Top命令优缺点 VisualVM软件、插件优缺点监控本地Java进程监控服务器的Java进程生产环境不推荐使用 Arthas优缺点**使用****arthas tunnel****管理所有的需要监控的程序** PrometheusGrafana监控优缺点阿里云环境搭建了解即可 如何判断有没有出现内存泄漏堆内存状况的对比正常情况出现内存泄漏处于持续增长的情况即使Minor GC也不能把大部分对象回收手动FULL GC之后的内存量每一次都在增长长时间观察内存曲线持续增长 查看那些对象导致的内存泄漏代码中的内存泄漏产生内存溢出原因一案例1equals()和hashCode()导致的内存泄漏问题测试原因解决方案 案例2内部类引用外部类非静态的内部类默认会持有外部类尽管代码上不再使用外部类匿名内部类对象如果在非静态方法中被创建会持有调用者对象垃圾回收时无法回收调用者 案例3ThreadLocal的使用案例4String的intern方法案例5通过静态字段保存对象案例6资源没有正常关闭 并发请求问题产生内存溢出原因二关键Jmeter介绍使用Jmeter进行并发测试发现内存溢出问题 文章说明 内存调优
内存溢出和内存泄漏
内存泄漏memory leak在Java中如果不再使用一个对象但是该对象依然在GC ROOT的引用链上这个对象就不会被垃圾回收器回收这种情况就称之为内存泄漏。
内存泄漏绝大多数情况都是由堆内存泄漏引起的所以后续没有特别说明则讨论的都是堆内存泄漏。
比如图中如果学生对象1不再使用 可以选择将ArrayList到学生对象1的引用删除即调用remove方法 如果整个集合都不再使用将对象A对ArrayList的引用删除A null这样所有的学生对象包括ArrayList都可以回收 但是如果不移除这两个引用中的任何一个学生对象1就属于内存泄漏了。
内存泄露带来什么问题
少量的内存泄漏可以容忍但是如果发生持续的内存泄漏就像滚雪球雪球越滚越大不管有多大的内存迟早会被消耗完最终导致的结果就是内存溢出。
产生内存溢出并不是只有内存泄漏这一种原因。 这些学生对象如果都不再使用越积越多就会导致超过堆内存的上限出现内存溢出。
正常情况的内存结构图如下 内存溢出出现时如下 内存泄漏的对象和依然在GC ROOT引用链上需要使用的对象加起来占满了内存空间无法为新的对象分配内存。
内存泄露案例演示
Arthas中使用dashboard -i 1000可以每隔一秒统计一下堆内存的使用情况 package com.itheima.jvmoptimize.leakdemo.demo3;import java.io.IOException;
import java.util.ArrayList;public class Outer{private byte[] bytes new byte[1024 * 1024]; //外部类持有数据private static String name 测试;static class Inner{private String name;public Inner() {this.name Outer.name;}}public static void main(String[] args) throws IOException, InterruptedException {
// System.in.read();int count 0;ArrayListInner inners new ArrayList();while (true){if(count % 100 0){Thread.sleep(10);}inners.add(new Inner());}}
}【运行】
Arthas统计的最后一次内存情况 内存泄漏的常见场景
场景一
大型的Java后端应用中在处理用户的请求之后没有及时将用户的数据删除。随着用户请求数量越来越多内存泄漏的对象占满了堆内存最终导致内存溢出。内存溢出会直接导致用户请求无法处理影响用户的正常使用。重启可以恢复应用使用治标不治本但是在运行一段时间之后依然会出现内存溢出。 【场景演示】
代码
package com.itheima.jvmoptimize.controller;import com.itheima.jvmoptimize.entity.UserEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;RestController
RequestMapping(/leak2)
public class LeakController2 {private static MapLong,Object userCache new HashMap();/*** 登录接口 放入hashmap中*/PostMapping(/login)public void login(String name,Long id){// 每次放300MuserCache.put(id,new byte[1024 * 1024 * 300]);}/*** 登出接口删除缓存的用户信息*/GetMapping(/logout)public void logout(Long id){userCache.remove(id);}}设置虚拟机参数将最大堆内存设置为1g: 在Postman中测试登录id为1的用户 调用logout接口id为1那么数据会正常删除 连续调用login传递不同的id但是不调用logout 调用几次之后就会出现内存溢出 场景二
分布式任务调度系统如Elastic-job、Quartz等进行任务调度时被调度的Java应用在调度任务结束中出现了内存泄漏导致多次调度之后内存溢出重启可以恢复应用使用但是在调度执行一段时间之后依然会出现内存溢出
开启定时任务 定时任务代码
package com.itheima.jvmoptimize.task;import com.itheima.jvmoptimize.leakdemo.demo4.Outer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;Component
public class LeakTask {private int count 0;private ListObject list new ArrayList();// 每隔100毫秒执行一次Scheduled(fixedRate 100L)public void test(){System.out.println(定时任务调用 count);// 这行代码存在内存泄露问题list.add(new Outer().newList());}
}import java.io.IOException;
import java.util.ArrayList;
import java.util.List;public class Outer {private byte[] bytes new byte[1024 * 1024 * 10];public ListString newList() {ListString list new ArrayListString() {{add(1);add(2);}};return list;}
}启动程序之后很快就出现了内存溢出 解决内存溢出的方法
解决内存溢出的步骤总共分为四个步骤其中前两个步骤是最核心的 常用内存监控工具
Top命令
top命令是linux下用来查看系统信息的一个命令它提供给我们去实时地去查看系统的资源比如执行时的进程、线程和系统参数等信息进程使用的内存为RES常驻内存- SHR共享内存系统负载是可以大于1的例如银行中每个窗口都被占用了但是还有的人在排队这些人也会被记录到负载中 上面的列表默认是按照CPU占用率降序排序的如果想要按照内存来降序排序需要先按CapsLock锁定大写然后再按下M通过这个方式可以快速知道哪个进程占用了大量内存 优缺点
优点
操作简单无额外的软件安装
缺点
只能查看最基础的进程信息无法查看到每个部分的内存占用堆、方法区、堆外 top命令只适合做初步的筛查
VisualVM
VisualVM是多功能合一的Java故障排除工具并且他是一款可视化工具整合了命令行 JDK 工具和轻量级分析功能功能非常强大。这款软件在Oracle JDK 6~8 中发布但是在 Oracle JDK 9 之后不在JDK安装目录下需要单独下载。下载地址https://visualvm.github.io/ 软件、插件
JDK8自带 更高版本的JDK直接下载最新版即可 【插件】
安装插件之后可以快速启动VisualVM 配置软件路径 使用 优缺点
优点
功能丰富实时监控CPU、内存、线程等详细信息支持Idea插件开发过程中也可以使用
缺点
对大量集群化部署的Java进程微服务项目需要手动进行管理一个一个去添加对应的服务器以及对应的JMX端口
监控本地Java进程 如果used逼近max说明内存快不够用了
监控服务器的Java进程生产环境不推荐使用
如果需要进行远程监控可以通过jmx方式进行连接。在启动java程序时添加如下参数
-Djava.rmi.server.hostname服务器ip地址
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port9122
-Dcom.sun.management.jmxremote.sslfalse
-Dcom.sun.management.jmxremote.authenticatefalse右键点击remote 填写服务器的ip地址 右键添加JMX连接 填写ip地址和端口号勾选不需要SSL安全验证 双击成功连接 生产环境不建议使用VisualVM来连接远程服务器因为其中的Perform GC手动GC和Heap Dump生成内存快照功能都会停止进程功能影响用户体验
Arthas
Arthas 是一款线上监控诊断产品通过全局视角实时查看应用 load、内存、gc、线程的状态信息并能在不修改应用代码的情况下对业务问题进行诊断包括查看方法调用的出入参、异常监测方法执行耗时类加载信息等大大提升线上问题排查效率。 优缺点
优点
功能强大不止于监控基础的信息还能监控单个方法的执行耗时等细节内容支持应用的集群管理
缺点
部分高级功能使用门槛较高
使用**arthas tunnel**管理所有的需要监控的程序
背景
小李的团队已经普及了arthas的使用但是由于使用了微服务架构生产环境上的应用数量非常多使用arthas还得登录到每一台服务器上再去操作非常不方便。为了解决这个问题可以使用tunnel来管理所有需要监控的程序。 步骤
1、在Spring Boot程序中添加arthas的依赖(只支持Spring Boot2.几的版本)在配置文件中添加tunnel服务端的地址便于tunnel去监控所有的程序
2、将tunnel服务端程序部署在某台服务器上并启动如果是生产环境尽量将tunnel服务部署单独的服务器上避免tunnel服务对线上业务造成影响
3、启动java程序微服务
4、打开tunnel的服务端页面查看所有的进程列表并选择进程进行arthas的操作。
在微服务的pom.xml添加依赖版本最好和使用的arthas版本号保持一致
dependencygroupIdcom.taobao.arthas/groupIdartifactIdarthas-spring-boot-starter/artifactIdversion3.7.1/version
/dependencyapplication.yml中添加配置:
arthas:#tunnel地址目前是部署在同一台服务器正式环境需要拆分 /ws是固定路径tunnel-server: ws://localhost:7777/ws#tunnel显示的应用名称直接引用应用名app-name: ${spring.application.name}#arthas http访问的端口和远程连接的端口http-port: 8888telnet-port: 9999在资料中找到arthas-tunnel-server.3.7.1-fatjar.jar上传到服务器并使用
nohup java -jar -Darthas.enable-detail-pagestrue arthas-tunnel-server.3.7.1-fatjar.jar 命令启动tunnel服务。-Darthas.enable-detail-pagestrue这个参数的作用是让tunnel提供一个页面展示内容默认是不提供的。通过服务器ip地址:8080/apps.html打开页面目前没有注册上来任何应用。 启动spring boot应用如果在一台服务器上注意区分端口。
-Dserver.porttomcat端口号
-Darthas.http-portarthas的http端口号
-Darthas.telnet-portarthas的telnet端口号端口号最终就能看到两个应用
单击应用就可以进入操作arthas了。 如果有服务没有注册上来查看nohup的日志文件看看启动有没有报错 PrometheusGrafana监控
PrometheusGrafana是企业中运维常用的监控方案
其中Prometheus用来采集系统或者应用的相关数据同时具备告警功能Grafana可以将Prometheus采集到的数据以可视化的方式进行展示 优缺点
优点
支持系统级别和应用级别的监控比如linux操作系统、Redis、MySQL、Java进程。监控范围广支持告警并允许自定义告警指标通过邮件、短信等方式尽早通知相关人员进行处理
缺点
环境搭建较为复杂一般由专业运维人员完成。java程序员责任看懂Grafana的图
阿里云环境搭建了解即可
这一小节主要是为了让同学们更好地去阅读监控数据所以提供一整套简单的环境搭建方式觉得困难可以直接跳过。企业中环境搭建的工作由运维人员来完成。
1、在pom文件中添加依赖
actuator通过http将一些指标向外暴露micrometer将java基本信息包括java虚拟机信息、数据库连接池信息、磁盘等信息收集起来并组装成prometheus可以识别的格式
dependencygroupIdio.micrometer/groupIdartifactIdmicrometer-registry-prometheus/artifactIdscoperuntime/scope
/dependency
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactIdexclusions!-- 去掉springboot默认配置 --exclusiongroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-logging/artifactId/exclusion/exclusions
/dependency2、添加配置项
management:endpoint:metrics:enabled: true #支持metricsprometheus:enabled: true #支持Prometheusmetrics:export:prometheus:enabled: truetags:application: jvm-test #实例名采集endpoints:web:exposure:include: * #开放所有端口即设置向外暴露的指标这两步做完之后启动程序。
3、通过地址ip地址:端口号/actuator/prometheus访问之后可以看到jvm相关的指标数据。 查看普罗米修斯相关内容 查看内存信息 查看所有bean对象
4、创建阿里云Prometheus实例 5、选择ECS服务 6、在自己的ECS服务器上找到网络和交换机 7、选择对应的网络 填写内容与ECS里边的网络设置保持一致 安全组和服务器里面的安全组保持一致 8、选中新的实例选择MicroMeter 想监控什么就安装相关的插件 9、给服务器ECS添加标签 10、填写内容注意ECS的标签 11、点击大盘就可以看到指标了 打开Grafana页面查看所有指标 12、指标内容: 如何判断有没有出现内存泄漏
堆内存状况的对比
正常情况
处理业务时会出现上下起伏业务对象频繁创建内存会升高触发MinorGC之后内存会降下来。手动执行FULL GC之后内存大小会骤降而且每次降完之后的大小是接近的。长时间观察内存曲线应该是在一个范围内。 出现内存泄漏处于持续增长的情况即使Minor GC也不能把大部分对象回收手动FULL GC之后的内存量每一次都在增长长时间观察内存曲线持续增长 查看那些对象导致的内存泄漏 代码中的内存泄漏产生内存溢出原因一
以下产生内存泄漏的原因均来自于java代码的不当处理
equals()和hashCode()不正确的equals()和hashCode()实现导致内存泄漏ThreadLocal的使用由于线程池中的线程不被回收导致的ThreadLocal内存泄漏内部类引用外部类非静态的内部类和匿名内部类的错误使用导致内存泄漏String的intern方法由于JDK6中的字符串常量池位于永久代intern被大量调用并保存产生的内存泄漏通过静态字段保存对象大量的数据在静态变量中被引用但是不再使用成为了内存泄漏资源没有正常关闭由于资源没有调用close方法正常关闭导致的内存溢出不太准确因为不一定导致内存泄漏
代码中的内存泄漏很容易暴露出来做一次压力测试就知道了
案例1equals()和hashCode()导致的内存泄漏
问题
在定义新类时没有重写正确的equals()和hashCode()方法默认使用Object的实现。在使用HashMap的场景下如果使用这个类对象作为keyHashMap在判断key是否已经存在时会使用这些方法如果重写方式不正确会导致相同的数据被保存多份。
测试
Student类没有重写equal和hashcode方法
package com.itheima.jvmoptimize.leakdemo.demo2;import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;import java.util.Objects;public class Student {private String name;private Integer id;private byte[] bytes new byte[1024 * 1024];public String getName() {return name;}public void setName(String name) {this.name name;}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}}
package com.itheima.jvmoptimize.leakdemo.demo2;import java.util.HashMap;
import java.util.Map;public class Demo2 {public static long count 0;public static MapStudent,Long map new HashMap();public static void main(String[] args) throws InterruptedException {while (true){if(count % 100 0){// 休眠一下让VisualVM获取数据不然CPU可能忙于执行程序Thread.sleep(10);}Student student new Student();student.setId(1);student.setName(张三);map.put(student,1L);}}
}运行之后通过visualvm观察 预测是大量学生对象加入hashmap中产生的问题 原因
正常情况
1、以JDK8为例首先调用hash方法计算key的哈希值hash方法中会使用到key这里是Student的对象的hashcode方法。根据hash方法的结果决定存放的数组中位置。
2、如果没有元素直接放入。如果有元素先判断key是否相等会用到equals方法如果key相等直接替换valuekey不相等走链表或者红黑树查找逻辑其中也会使用equals比对是否相同。 异常情况
1、hashCode方法实现不正确按照Object默认实现采用一个随机数三个确定的值运算出来会导致相同id的学生对象计算出来的hash值不同可能会被分到不同的槽中。 2、equals方法实现不正确会导致key在比对时即便学生对象的id是相同的也被认为是不同的key。 3、长时间运行之后HashMap中会保存大量相同id的学生数据。 解决方案
1、在定义新实体时始终重写equals()和hashCode()方法。
2、重写时一定要确定使用了唯一标识去区分不同的对象比如用户的id等。
3、hashmap使用时尽量使用编号id等数据作为key效率更高不要将整个实体类对象作为key存放。 equals方法用哪些字段来判断 代码
package com.itheima.jvmoptimize.leakdemo.demo2;import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;import java.util.Objects;public class Student {private String name;private Integer id;private byte[] bytes new byte[1024 * 1024];public String getName() {return name;}public void setName(String name) {this.name name;}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}Overridepublic boolean equals(Object o) {if (this o) {return true;}if (o null || getClass() ! o.getClass()) {return false;}Student student (Student) o;return new EqualsBuilder().append(id, student.id).isEquals();}Overridepublic int hashCode() {return new HashCodeBuilder(17, 37).append(id).toHashCode();}
}【测试】 为什么Student对象还是有这么多呢不应该只有一个吗 原因垃圾回收需要时间这些对象没有及时被垃圾回收
案例2内部类引用外部类
非静态的内部类默认会持有外部类尽管代码上不再使用外部类
所以如果有地方引用了这个非静态内部类会导致外部类也被引用垃圾回收时无法回收这个外部类
package com.itheima.jvmoptimize.leakdemo.demo3;import java.io.IOException;
import java.util.ArrayList;public class Outer{private byte[] bytes new byte[1024 * 1024]; //外部类持有数据private String name 测试;class Inner{private String name;public Inner() {// 获取外部类的属性值赋值给内部类的属性this.name Outer.this.name;}}public static void main(String[] args) throws IOException, InterruptedException {
// System.in.read();int count 0;// 只在集合里面存储内部类对象外部类不再使用ArrayListInner inners new ArrayList();while (true){if(count % 100 0){Thread.sleep(10);}inners.add(new Outer().new Inner());}}
}外部类对象为内存泄漏对象运行一段时间就溢出了 为什么外部类对象会一直被保留下来 这个外部类对象在GC Root引用链上面所以不会被回收
【解决方案】
这个案例中使用内部类的原因是可以直接获取到外部类中的成员变量值简化开发。如果不想持有外部类对象应该使用静态内部类 package com.itheima.jvmoptimize.leakdemo.demo3;import java.io.IOException;
import java.util.ArrayList;public class Outer{private byte[] bytes new byte[1024 * 1024]; //外部类持有数据private static String name 测试;static class Inner{private String name;public Inner() {this.name Outer.name;}}public static void main(String[] args) throws IOException, InterruptedException {
// System.in.read();int count 0;ArrayListInner inners new ArrayList();while (true){if(count % 100 0){Thread.sleep(10);}inners.add(new Inner());}}
}匿名内部类对象如果在非静态方法中被创建会持有调用者对象垃圾回收时无法回收调用者
package com.itheima.jvmoptimize.leakdemo.demo4;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;public class Outer {private byte[] bytes new byte[1024 * 1024 * 10];public ListString newList() {# 这里使用了匿名内部类来初始化 list 变量。匿名内部类没有显式的类名# 它是一个实现了 ArrayListString 接口实际上是继承自 ArrayListString 类的未命名子类。ListString list new ArrayListString() {{add(1);add(2);}};return list;}public static void main(String[] args) throws IOException {System.in.read();int count 0;ArrayListObject objects new ArrayList();while (true){System.out.println(count);// 这里创建的Outer对象不能被回收objects.add(new Outer().newList());}}
}查看字节码文件 Outer$1匿名内部类 【解决方案】
使用静态方法可以避免匿名内部类持有调用者对象。 package com.itheima.jvmoptimize.leakdemo.demo4;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;public class Outer {private byte[] bytes new byte[1024 * 1024 * 10];public static ListString newList() {ListString list new ArrayListString() {{add(1);add(2);}};return list;}public static void main(String[] args) throws IOException {System.in.read();int count 0;ArrayListObject objects new ArrayList();while (true){System.out.println(count);objects.add(newList());}}
}不再持有调用者对象 案例3ThreadLocal的使用
问题
ThreadLocal用来存储线程里面的本地变量每个线程之间的变量隔离不会相互影响。如果仅仅使用手动创建的线程(new的方式)就算没有调用ThreadLocal的remove方法清理数据也不会产生内存泄漏。因为当线程被回收时ThreadLocal也同样被回收。但是如果使用线程池就不一定了。
【直接new】
package com.itheima.jvmoptimize.leakdemo.demo5;import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class Demo5_1 {public static ThreadLocalObject threadLocal new ThreadLocal();public static void main(String[] args) throws InterruptedException {while (true) {new Thread(() - {threadLocal.set(new byte[1024 * 1024 * 10]);}).start();Thread.sleep(10);}}
}没有发生内存泄漏 【使用线程池】
package com.itheima.jvmoptimize.leakdemo.demo5;import java.util.concurrent.*;public class Demo5 {public static ThreadLocalObject threadLocal new ThreadLocal();public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor threadPoolExecutor new ThreadPoolExecutor(Integer.MAX_VALUE, Integer.MAX_VALUE,0, TimeUnit.DAYS, new SynchronousQueue());int count 0;while (true) {System.out.println(count);threadPoolExecutor.execute(() - {threadLocal.set(new byte[1024 * 1024]);});Thread.sleep(10);}}
}解决方案
线程方法执行完一定要调用ThreadLocal中的remove方法清理对象。
package com.itheima.jvmoptimize.leakdemo.demo5;import java.util.concurrent.*;public class Demo5 {public static ThreadLocalObject threadLocal new ThreadLocal();public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor threadPoolExecutor new ThreadPoolExecutor(Integer.MAX_VALUE, Integer.MAX_VALUE,0, TimeUnit.DAYS, new SynchronousQueue());int count 0;while (true) {System.out.println(count);threadPoolExecutor.execute(() - {threadLocal.set(new byte[1024 * 1024]);threadLocal.remove();});Thread.sleep(10);}}
}案例4String的intern方法
问题
JDK6中字符串常量池位于堆内存中的Perm Gen永久代中如果不同字符串的intern方法被大量调用字符串常量池会不停的变大超过永久代内存上限之后就会产生内存溢出问题。
package com.itheima.jvmoptimize.leakdemo.demo6;import org.apache.commons.lang3.RandomStringUtils;import java.util.ArrayList;
import java.util.List;public class Demo6 {public static void main(String[] args) {while (true){ListString list new ArrayListString();int i 0;while (true) {// 每次循环创建一个字符串放到常量池中String.valueOf(i).intern(); //JDK1.6 perm gen 不会溢出}}}
}测试发现上述代码永久代内存不会溢出因为内存满的话会执行垃圾回收
package com.itheima.jvmoptimize.leakdemo.demo6;import org.apache.commons.lang3.RandomStringUtils;import java.util.ArrayList;
import java.util.List;public class Demo6 {public static void main(String[] args) {while (true){ListString list new ArrayListString();int i 0;while (true) {// 产生了引用关系之后就不会被回收了list.add(String.valueOf(i).intern()); //溢出}}}
}JDK6测试 JDK8字符串常量池放在堆里面测试 解决方案
1、注意代码中的逻辑尽量不要将随机生成的字符串加入字符串常量池
2、增大永久代空间的大小根据实际的测试/估算结果进行设置-XX:MaxPermSize256M
案例5通过静态字段保存对象
问题
如果大量的数据在静态变量中被长期引用数据就不会被释放如果这些数据不再使用就成为了内存泄漏。
解决方案
1、尽量减少将对象长时间的保存在静态变量中如果不再使用必须将对象删除比如在集合中或者将静态变量设置为null。
2、使用单例模式时尽量使用懒加载如果该类没有使用不会创建对象而不是立即加载。
package com.itheima.jvmoptimize.leakdemo.demo7;import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;Lazy //懒加载
Component
public class TestLazy {private byte[] bytes new byte[1024 * 1024 * 1024];
}将内存上限设置为500一旦使用这个对象就会报错如果没有添加Lazy注解不使用也会报错 3、Spring的Bean中不要长期存放大对象如果是缓存用于提升性能尽量设置过期时间定期失效。
package com.itheima.jvmoptimize.leakdemo.demo7;import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;import java.time.Duration;public class CaffineDemo {public static void main(String[] args) throws InterruptedException {CacheObject, Object build Caffeine.newBuilder()// 设置100ms之后就过期.expireAfterWrite(Duration.ofMillis(100)).build();int count 0;while (true){build.put(count,new byte[1024 * 1024 * 10]);Thread.sleep(100L);}}
}案例6资源没有正常关闭
问题
连接和流这些资源会占用内存如果使用完之后没有关闭这部分内存不一定会出现内存泄漏。
package com.itheima.jvmoptimize.leakdemo.demo1;import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.sql.*;//-Xmx50m -Xms50m
public class Demo1 {// JDBC driver name and database URLstatic final String JDBC_DRIVER com.mysql.cj.jdbc.Driver;static final String DB_URL jdbc:mysql:///bank1;// Database credentialsstatic final String USER root;static final String PASS 123456;public static void leak() throws SQLException {//Connection conn null;Statement stmt null;Connection conn DriverManager.getConnection(DB_URL, USER, PASS);// executes a valid querystmt conn.createStatement();String sql;sql SELECT id, account_name FROM account_info;ResultSet rs stmt.executeQuery(sql);//STEP 4: Extract data from result setwhile (rs.next()) {//Retrieve by column nameint id rs.getInt(id);String name rs.getString(account_name);//Display valuesSystem.out.print(ID: id);System.out.print(, Name: name \n);}}public static void main(String[] args) throws InterruptedException, SQLException {while (true) {leak();}}
}同学们可以测试一下这段代码会不会产生内存泄漏应该是不会的因为后面conn对象不使用了不再处于GC Root引用链中会被回收。同理rs这些也会被回收。但是这个结论不是确定的所以建议编程时养成良好的习惯尽量关闭不再使用的资源。
解决方案
1、为了防止出现这类的资源对象泄漏问题必须在finally块中关闭不再使用的资源。
2、从 Java 7 开始使用try-with-resources语法可以用于自动关闭资源。 并发请求问题产生内存溢出原因二关键
通过发送请求向Java应用获取数据正常情况下Java应用将数据返回之后这部分数据就可以在内存中被释放掉。
接收到请求时创建对象: 响应返回之后对象就可以被回收掉 并发请求问题指的是由于用户的并发请求量有可能很大同时处理数据的时间很长导致大量的数据存在于内存中最终超过了内存的上限导致内存溢出。这类问题的处理思路和内存泄漏类似首先要定位到对象产生的根源。SpringBoot里面的tomcat线程池的线程最多只有200个所以同时只能处理200个请求 解决方案
找到执行时间长、占用内存大的接口看看怎么优化代码
Jmeter介绍
使用Apache Jmeter软件可以进行并发请求测试。Apache Jmeter是一款开源的测试软件使用Java语言编写最初是为了测试Web程序目前已经发展成支持数据库、消息队列、邮件协议等不同类型内容的测试工具。
Jmeter可以模拟Http并发请求Apache Jmeter支持插件扩展生成多样化的测试结果如TPS、响应时间
使用Jmeter进行并发测试发现内存溢出问题
背景
小李的团队发现有一个微服务在晚上8点左右用户使用的高峰期会出现内存溢出的问题于是他们希望在自己的开发环境能重现类似的问题。
步骤
1、安装Jmeter软件添加线程组。
打开资料中的Jmeter找到bin目录双击jmeter.bat启动程序。 创建线程组在线程组中增加Http请求添加随机参数。 添加线程组参数
线程数并发线程的数量Ramp-Up时间上面的线程在多少时间启动完成。如果希望所有线程一开始就工作就设置为0循环次数每个线程调用多少次请求勾选永远就会持续发送请求 在线程组中添加Http请求 添加http参数 接口代码
/*** 大量数据 处理慢*/
GetMapping(/test)
public void test1() throws InterruptedException {// 100m模拟大量数据byte[] bytes new byte[1024 * 1024 * 100];// 模拟处理慢Thread.sleep(10 * 1000L);
}在线程组中添加监听器 – 聚合报告用来展示最终结果。 启动程序运行线程组并观察程序是否出现内存溢出。
添加虚拟机参数 点击运行 很快就出现了内存溢出 【内存泄漏案例】
1、设置线程池参数 2、设置http接口参数 3、代码
/*** 登录接口 传递名字和id,放入hashmap中*/
PostMapping(/login)
public void login(String name,Long id){// userCache是一个hashMap (静态变量中存放大量数据)userCache.put(id,new UserEntity(id,name));
}4、我们想生成随机的名字和id,选择函数助手对话框 5、选择Random随机数生成器
6、让随机数生成器生效值中直接ctrl v就行已经被复制到粘贴板了。 7、字符串也是同理的设置方法 8、添加name字段 9、点击测试一段时间之后同样出现了内存溢出 文章说明
该文章是本人学习 黑马程序员 的学习笔记文章中大部分内容来源于 黑马程序员 的视频黑马程序员JVM虚拟机入门到实战全套视频教程java大厂面试必会的jvm一套搞定丰富的实战案例及最热面试题也有部分内容来自于自己的思考发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识如有侵权请联系删除最后对 黑马程序员 的优质课程表示感谢。