目录
1:首先我们需要知道JVM的参数设置
1.1:jvm信息输出
public class JvmTest1 {
/**
* 虚拟机参数设置
*
* -XX:+PrintGCDetails 输出虚拟机的堆、栈、元空间等信息
*/
public static void main(String[] args) {
}
}
结果截图:
结果分析:
PSYoungGen(年轻代):total 76288K ,Parallel Scavenge 并行垃圾回收器
eden space 65536K 伊甸区
from space 10752K, from区
to space 10752K, to区
ParOldGen(老年代):total 175104K,Parallel Old 并行垃圾回收器
年轻代+老年代=76288K+175105K=251392K=245.5MB 约等于作者本机16GB的1/64
堆默认情况下,初始堆内存大小为:电脑内存大小/64,但是不绝对,根据内存动态来的
堆默认情况下,最大堆内存大小为:电脑内存大小/4,但是不绝对,根据内存动态来的
Metaspace(元空间) : 初始值根据系统来设置,默认没有上限
1.2:验证jvm默认值
代码如下:
public class JvmTest2 {
public static void main(String[] args) {
//返回Java虚拟机中的内存总量。
Long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("TOTAL_ MEMORY = "+totalMemory +"(字节)"+(totalMemory/1024/1024) + "MB");
//返回Java虚拟机试图使用的最大内存量。
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("MAX_MEMORY ="+maxMemory +"(字节)、"+(maxMemory/1024/1024) + "MB");
}
}
输出结果如下:
2:Jvm内存设置
2.1:堆内存设置(默认年轻代:老年代=1:2)
public class JvmHeadTest1 {
/**
* jvm的堆内存设置
* -Xms10m 初始堆容量大小
* -Xmx10m 堆容量最大值
* -XX:+PrintGCDetails
*
* -Xms10m -Xmx10m -XX:+PrintGCDetails
*/
public static void main(String[] args) {
}
}
截图:
堆内存重点分析:
我们设置的堆的最大和初始容量都是10M
年轻代=2048+512+512=3M
老年代=7168K=7M
年轻代+老年代=2048+512+512+7168=10240K=10m
年轻代:老年代=3:7 约等于1:2(10M不能满足默认的的1:2,设置9M的时候可以看到)
年轻代的伊甸、from、to分别是2048、512、521。比例是4:1:1
总结:年轻代小,老年代大,默认是1:2
2.2:堆内存设置(自定义年轻代和老年代)
有两种方法
1:-Xmn 直接设置年轻代的大小,总共容量默认10m,设置新生代4m
public class JvmHeadTest1 {
/**
* jvm的堆内存设置
* -Xms10m 初始堆容量大小
* -Xmx10m 堆容量最大值
* -Xmn4m 直接设置新生代大小(优先级高于比例设置)
* -XX:NewRatio=4 新生代:老年代=1:4 默认值是2也就是1:2
* -XX:+PrintGCDetails
*
* 1:-Xms10m -Xmx10m -XX:NewRatio=4 -XX:+PrintGCDetails (NewRatio设置新生代、老年代比例)
*
* 2:-Xms10m -Xmx10m -Xmn4m -XX:NewRatio=4 -XX:+PrintGCDetails (Xmn4m直接设置新生代大小,优先级高于比例设置)
*/
public static void main(String[] args) {
}
}
截图分析:
2:-XX:NewRatio=4 设置年轻代和老年代的比值,默认是1:2,总共容量默认10m,这里设置1:4
设置新的比例
public class JvmHeadTest1 {
/**
* jvm的堆内存设置
* -Xms10m 初始堆容量大小
* -Xmx10m 堆容量最大值
* -XX:NewRatio=4 新生代:老年代=1:4 默认值是2也就是1:2
* -XX:+PrintGCDetails
*
* -Xms10m -Xmx10m -XX:NewRatio=4 -XX:+PrintGCDetails
*/
public static void main(String[] args) {
}
}
截图分析:
2.3:设置新生代的伊甸、to、from大小
设置年轻代中Eden区与Survivor区的比值。表示2个Survivor区(JVM堆内存年轻代中默认有2个大小相等的Survivor区)与1个Eden区的比值为2:4,即1个Survivor区占整个年轻代大小的1/6。
1:这里设置SurvivorRatio=4,eden:from:to=4:1:1
public class JvmHeadTest2 {
/**
* jvm的堆内存设置
* -XX:SurvivorRatio=4 伊甸:from:to=4:1:1
*
* -XX:SurvivorRatio=4 -XX:+PrintGCDetails
*/
public static void main(String[] args) {
}
}
截图:
2:这里设置SurvivorRatio=6,eden:from:to=6:1:1
public class JvmHeadTest2 {
/**
* jvm的堆内存设置
* -XX:SurvivorRatio=6 伊甸:from:to=6:1:1
*
* -XX:SurvivorRatio=6 -XX:+PrintGCDetails
*/
public static void main(String[] args) {
}
}
截图:
3:栈内存设置默认是1M,-Xss2m设置
1:默认栈1M,我们查看代码
public class StackTest1 {
/**
* -Xss1m -XX:+PrintGCDetails
* -Xss1m 每个线程申请的栈的内存是1m,14000的阶乘会报错
*/
public static void main(String[] args) {
long n=14000;
long add = add(n);
System.out.println(add);
}
/**
* 阶乘算法,n太大,会出现StackOverflowError
* @param n
* @return
*/
public static long add(long n) {
if (n <= 1) {
return n; // 终止条件
} else {
return n * add(n - 1); // 递归调用
}
}
}
报错截图:StackOverflowError
修改栈的大小
-Xss2m -XX:+PrintGCDetails
这时候在测试,不报错了,线程的栈空间更大,能容纳更多此次的递归调用。
4:元空间大小设置默认21M,自动扩展
默认的 MetaspaceSize=21807104=21M
原来JDK8中,XX:MaxMetaspaceSize确实是没有上限的,最大容量与机器的内存有关;但是XX:MetaspaceSize是有一个默认值的:21M
1:字段定义元空间大小
-XX:MetaspaceSize=200m -XX:MaxMetaspaceSize=200m -XX:+PrintGCDetails
public class MetaspaceSizeTest1 {
/**
*
* -XX:MetaspaceSize=200m -XX:MaxMetaspaceSize=200m -XX:+PrintGCDetails
* 自定义元空间的大小
*/
public static void main(String[] args) {
}
}
输出结果好像没有变化?挠头
5:其它JVM参数
-XX:MaxTenuringThreshold=15 对象在年轻代的GC回收次数,超过15次没有回收,进入老年代
6:JVM调优
6.1:visualvm调优
注意:在高版本的 JDK 中(JDK >= 9) 已经移除了该文件,如果高版本的 JDK 需要使用该工具,需要额外下载,下载地址:VisualVM: Download,下载完成后双击 bin 目录下的 visualvm.exe
文件启动 VisualVM
VisualVm是一个功能强大的多合一故障诊断工具和性能监控可视化工具。这款工具不仅提供了内存使用、线程活动和 CPU 占用等方面的实时数据,还能通过分析堆转储(heap dump)来识别内存泄漏。尽管 Java VisualVM 的功能强大,但很多开发者在启动和使用过程中会感到困惑。本文将介绍如何打开 Java VisualVM,并用实例说明如何解决一个实际问题
主要有两个途径
1:在idea中进行安装VisualVm插件
2:在自己的电脑中独自安装
补充:需要安装VisualGC插件便于查看堆的内存信息
6.2:实时监控内存溢出OOM
参数设置 -Xms100m -Xmx100m -Xmn40m -XX:+PrintGCDetails
/**
* 模拟内存占用高
* -Xms100m -Xmx100m -Xmn40m -XX:+PrintGCDetails
* 堆内存初始值和最大都是100M 年轻代40M
*
* java.lang.OutOfMemoryError: Java heap space
*/
@GetMapping(value = "get2")
public List<User> select(@RequestParam(value = "id",required = false) Integer id) {
List<User> users = new ArrayList<>();
//死循环 很快就是把堆内存占满
while (true){
User user = new User();
users.add(user);
}
}
发送请求截图分析:
6.3:日志监控内存溢出OOM案例
实时监控优缺点,发生OOM的时候我们不一定能及时看到。因此我们希望在服务器发生OOM的时候能保存日志,便于追踪分析
1.设置参数在异常发生时自动生成dump文件。
-XX:+HeapDumpOnOutOfMemoryError 表示当JVM发生OOM时,自动生成DUMP文件。
-XX:HeapDumpPath=存储文件/目录 表示生成DUMP文件的路径
java代码:
/**
* 模拟内存占用高
* -Xms100m -Xmx100m -Xmn40m -XX:+PrintGCDetails
* 堆内存初始值和最大都是100M 年轻代40M
*
* java.lang.OutOfMemoryError: Java heap space
*
* -Xms100m
* -Xmx100m
* -Xmn40m
* -XX:+HeapDumpOnOutOfMemoryError 设置oom的时候自动输出dump
* -XX:HeapDumpPath=/Users/huyiju/Desktop/JVM/a.dump 设置文件位置
* -XX:+PrintGCDetails
*/
@GetMapping(value = "get2")
public List<User> select2() {
List<User> users = new ArrayList<>();
//很快就是把堆内存占满
while (true){
User user = new User();
users.add(user);
}
}
分析截图:查看那些对象OOM了
6.4:CPU占用高的时候
Java代码:
/**
* 模拟cpu占用高
* @param id
*/
@GetMapping(value = "get3")
public void select3(@RequestParam(value = "id",required = false) Integer id) {
while (true){
}
}
vm分析:
精准定位到方法,该方法是死循环。
6.5:服务器cpu占用高
第1步,使用top命令找到占用CPU高的进程。
第2步,使用ps –mp命令找到进程下占用CPU高的线程ID。
第3步,使用printf命令将线程ID转换成十六进制数。
第4步,使用jstack命令输出线程运行状态的日志信息。
https://zhuanlan.zhihu.com/p/641072716