【高级篇】Java JVM实战 之 内存调优
@[TOC]
一、通过Jprofiler调式Dump文件错误
⛅ 什么是Jprofiler?
JProfiler 是一个 用于分析运行JVM内部情况的专业工具。 在开发中你可以使用它,用于质量保证,也可以解决你的生产系统遇到的问题
JProfiler处理四个主要问题:
方法调用
这通常被称为"CPU分析"。方法调用可以通过不同的方式进行测量和可视化, 分析方法调用可以帮助了解你的应用程序正在做什么,并找到提高其性能的方法。
分配
分析堆上对象的分配、引用链和垃圾回收属于"内存分析"的范畴。 这个功能可以让你解决内存泄漏,总之使用更少的内存,分配更少的临时对象。
线程和锁
线程可以持有锁,例如通过在一个对象上做同步。当多个线程协作时,可能会出现死锁,JProfiler可以为你可视化这种情况。 此外,锁可能被争用,这意味着线程在获得锁之前必须等待。通过JProfiler可以深入了解线程及其各种锁情形。
高层子系统
许多性能问题发生在更高的语义层面。例如,对于JDBC调用,你可能想找出哪条SQL语句是最慢的。 对于这样的子系统,JProfiler提供了"探针",将特定有效载荷附加到调用树。
JProfiler的UI是一个桌面应用程序。你可以以交互的方式实时分析JVM,也可以在不使用UI的情况下,自动化分析。 保存在快照中的分析数据,可以通过JProfiler UI打开。此外,命令行工具和构建工具集成可以帮助你自动分析会话。
⚡使用Jprofiler调试Dump文件
OOM错误
import java.util.ArrayList;
//-Xms 设置初始化内存大小,默认是1/64
//-Xms 设置最大分配内存,默认是1/4
//-XX:+PrintGCDetail 打印GC垃圾回收的相关信息
//-XX:+HeapDumpOnOutOfMemoryError 打印OOM的错误,并Dump
//-Xms1m -Xms8m -XX:+HeapDumpOnOutOfMemoryError
public class Demo03 {
byte[] bytes = new byte[1*1024*1024];
public static void main(String[] args) {
ArrayList<Demo03> objects = new ArrayList<>();
int count = 0;
try {
while (true) {
objects.add(new Demo03());
count++;
}
} catch (Exception e) {
System.out.println(count);
System.out.println(e.getMessage());
}
}
}
下载JProfiler
无脑式下一步安装即可
然后IDEA下载 Jprofiler插件
下载完后程序我们去tools配置下载的客户端Jprofiler
IDEA会自动配置
在执行程序时加上以下参数
执行完毕后会dump下来文件
打开项目根目录找到dump文件
打开分析错误
二、堆内存调优
import java.util.Random;
public class Test {
public static void main(String[] args) {
//返回虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();
//返回虚拟机的初始化内存
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("虚拟机试图使用的最大内存:" + max + "字节," + ((double)max/1024/1024) + "MB");
System.out.println("虚拟机的初始化内存:" + totalMemory + "字节," + ((double)totalMemory/1024/1024) + "MB");
//在启动时配置JVM参数
//-Xms8m -Xms8m -XX:+PrintGCDetails
//伊甸园区+老年区=堆内存 元空间 逻辑上存在,物理上不存在
//305664K + 699392K = 1,005,056K = 981.5MB
//默认情况下,分配的总内存 是电脑内存的 1/4 而初始内存是 1/64
//OOM错误解决
//1.尝试扩大堆内存,看结果
//2.分析内存,看那个地方出现了问题(专业工具)
}
}
设置JVM参数启动
import java.util.Random;
public class Test {
public static void main(String[] args) {
//返回虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();
//返回虚拟机的初始化内存
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("虚拟机试图使用的最大内存:" + max + "字节," + ((double)max/1024/1024) + "MB");
System.out.println("虚拟机的初始化内存:" + totalMemory + "字节," + ((double)totalMemory/1024/1024) + "MB");
//-Xms8m -Xms8m -XX:+PrintGCDetails
//伊甸园区+老年区=堆内存 元空间 逻辑上存在,物理上不存在
//305664K + 699392K = 1,005,056K = 981.5MB
}
}
设置JVM参数
单击 modiy options
选择JVM参数
写入参数名称
Apply同意并保存
执行代码
我们可以得出,伊甸园区内存大小+老年代内存大小=981.5
所以说,元空间,逻辑上存在,物理上不存在
三、 GC垃圾回收器
JVM在进行GC垃圾回收时,并不是对这三个区域同意回收,大部分回收都是新生代
- 新生代
- 幸存区(from to)
- 老年区
GC两种类型:轻GC(普通的GC) 重GC(全局GC)
GC题目:
- JVM的内存模型和分区,详细到每个区放什么?
- 堆里面的区有哪些?Eden,form,to,老年区,说说他们的特点!
- GC的算法有哪些?标记清除法,标记压缩,复制算法,引用计数器 怎么用的?
- 轻GC和重GC分别在什么时候发生?
四、GC常用算法
❄️引用计数法
⛄复制算法
-
好处:没有内存的碎片
-
坏处:浪费了内存空间,多了一半空间永远是空的。假设对象100%存活(极端情况)
复制算法最佳使用场景:对象存活度较低的时候;新生区~
♨️标记清除算法
优点: 不需要额外的空间,把复制算法的缺点弥补了
缺点: 两次扫描严重浪费时间,会产生内存碎片,hash定位需要成本
⛽标记压缩
再次优化
⚠️标记清除压缩
先标记清除几次
再压缩
内存效率:复制算法 —> 标记清除算法 —> 标记压缩算法(时间复杂度)
内容整齐度:复制算法 = 标记压缩算法 —> 标记清除算法
内容利用率:标记压缩算法 = 标记清除算法 —> 复制算法
没有最优算法吗?
没有,没有最好的算法,只有最合适的算法, —> GC:分代收集算法
年轻代:
- 存活率低
- 复制算法
老年代:
- 区域大:存活率高
- 标记清除(内存碎片不是太多)+标记压缩混合实现
五、JMM
-
什么是JMM:Java Memory Model
-
它是干嘛的?:官方,博客,对应的视频
作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到规则)
JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存中(Main Memory)中,
每个线程都有一个私有的工作内存(Local Memory)
解决共享对象可见性的问题:volatile
-
它该如何学习
JMM是一个抽象的概念,理论
可见性
volatile关键字
指令重排
关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:
- lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
- 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
- 不允许read和load、store和write操作之一单独出现
- 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。
⛵小结
以上就是【Bug 终结者】对 【高级篇】Java JVM实战 之 内存调优 的简单介绍,JVM 内存优化,偏底层知识,认真研究,虚心进步,加油,走过的艰难终将铺向成功的道路,学习JVM可了解Java程序的运行方式以及运行原理,更深层次的理解可提高自己对Java知识再上高峰,JVM 内存调优,进阶必备!
如果这篇【文章】有帮助到你,希望可以给【Bug 终结者】点个赞👍,创作不易,如果有对【后端技术】、【前端领域】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!
- 点赞
- 收藏
- 关注作者
评论(0)