【Java虚拟机】JVM核心基础和常见参数实战

简介: 【Java虚拟机】JVM核心基础和常见参数实战

1.新版JVM内存组成部分和堆空间分布

JVM内存的5大组成(基于JDK8的HotSpot虚拟机,不同虚拟机不同版本会有不一样)

名称 作用 特点
程序计数器 也叫PC寄存器,用于记录当前线程执行的字节码指令位置,以便线程在恢复执行时能够从正确的位置开始 线程私有
Java虚拟机栈 用于存储Java方法执行过程中的局部变量、方法参数和返回值,以及方法执行时的操作数栈 线程私有
本地方法栈 用于存储Java程序调用本地方法的参数和返回值等信息。 线程私有
用于存储Java程序创建的对象,所有线程共享一个堆,堆中的对象可以被垃圾回收器回收,以便为新的对象分配空间 线程共享
元数据区 用于存储类的元数据信息,如类名、方法名、字段名等,以及动态生成的代理类、动态生成的字节码等
元空间是位于本地(直接)内存中的,而不是像JDK8之前方法区位于堆内存中的。
线程共享


3fcbf77c4b7040439a2a906bd28811f3.png

堆空间内存分布

  • 用于存储Java程序创建的对象,所有线程共享一个堆
  • 堆中的对象可以被垃圾回收器回收,以便为新的对象分配空间


e6a2849355f4464fbf000332c9961e12.png

2.JVM堆空间垃圾回收流程

(1)面试题:说下JVM里面堆内存划分和堆内存垃圾回收流程

  • 新建对象,放到Eden区,满后触发Minor GC(每次都是由Eden区满触发Minor GC,接连放对象到S0或S1)
  • 存活的对象移动到Survivor的S0区,如果S0满后触发Minor GC
  • S0存活下来的对象移动到S1区,然后S0区空闲
  • S1满后触发Minor GC,再次移动到S0区,然后S1区空闲
  • 反复GC每次对象涨1岁,到达一定次数后(默认15),进入老年代
  • 当老年代内存不足会触发Full GC,出现STW(Stop-The-World)
  • 堆被垃圾回收,基本都是采用分代收集算法,不通区域的采用不同的垃圾回收算法
  • 方法结束后,堆中的对象不会马上移除,在垃圾回收的时候才会被移除

(2)面试题:堆空间里面分配比例如何


5b0285d86dc64bc2971ce8057a17e4d1.png

官方推荐一般老年代与新生代的占比为2:1,即老年代占整个堆空间的2/3,新生代占整个堆空间的1/3,在Yong区又分三个区域 Eden、Survivor-0、Survivor-1,Eden分整个Yong的8/10,两个Survivor各占1/10。

3.JVM内存垃圾回收相关参数

(1)JVM参数格式分类

格式 解释 例子
标准参数(-) 所有JVM都实现这些参数的功能 -verbose:gc 打印GC简要信息
非标准参数(-X) 不保证所有JVM实现都满足 -Xmx2048m等价 -XX:MaxHeapSize JVM最大堆内存为2048M
非稳定参数(-XX) 不稳定未来可能取消,但很有用 -XX:+PrintGCDetails每次GC时打印详细信息。
-XX:+ 开启对应的参数 -XX:+PrintGCDetails 开启每次GC时打印详细信息。
-XX:- 关闭对应的参数 -XX:-DisableExplicitGC 禁止调用System.gc()
-XX:= 设定数字参数 -XX:NewRatio=2 新生代和老年代内存比例

(2)JVM堆栈内存配置参数

参数 解释
-Xms 初始堆大小,推荐和最大堆一样
-Xmx 最大堆大小,推荐和初始堆一样
-Xmn 年轻代大小
-Xss 每个线程的栈大小

(3)JVM常见的命令行参数配置

参数 解释
-XX:+PrintGCDetails 打印GC回收信息
-XX:NewRatio 新生代和老年代空间大小的比率,由-XX:NewRatio参数控制
-XX:NewRatio参数的默认值是2,表示新生代和老年代的比例是1:2
如果将-XX:NewRatio设置为4,表示新生代和老年代的比例是1:4
-XX:MaxMetaspaceSize 元空间所分配内存的最大值,默认没限制
-XX:+UseConcMarkSweepGC 设置并发收集器

4.JVM虚拟机栈参数调整案例实战

JVM虚拟机栈

  • 用来存储Java程序中的方法调用和局部变量的内存区域
  • 每个线程都有自己的虚拟机栈,其生命周期与线程相同
  • 当一个方法被调用时,Java虚拟机会在该线程的虚拟机栈中创建一个栈帧,用来存储该方法的局部变量、方法返回值等信息
  • 异常情况
  • 默认情况下,JVM虚拟机栈的大小是固定的,JDK1.5后通常为1MB
  • 如果线程在执行方法时需要更多的栈空间,JVM会抛出StackOverflowError异常
  • JVM参数 xss,比如 -Xss1m 表示1MB

(1)案例:模拟递归调用,对count一直++,直到栈溢出

public class StackFrameDemo {
    private static int count = 0;
    public static void main(String[] args) {
        try {
            recursiveMethod();
        } catch (Throwable t) {
            System.out.println("Stack overflow after " + count + " invocations.");
            t.printStackTrace();
        }
    }
    private static void recursiveMethod() {
        count++;
        recursiveMethod();
    }
}
  • 配置栈大小,最少208k,低于208k启动不起来项目,我们这块配置 524k,-Xss524k

6a5c047398e446acaaaf280912b6e4c1.png

  • 再次测试


53d60b8a152e4d4291db6f1ca5438d44.png

  • 结论:
  • 栈越小,递归调用的次数就越少,因为栈空间不足导致栈溢出异常
  • 栈越大,递归调用的次数就越多,因为有足够的栈空间来存储方法调用的信息

5.JVM堆参数调整压测案例实战

  • 需求
  • 通过调整不同的JVM堆参数,查看相关指标
  • 测试接口
@RestController
@RequestMapping("api/v1/data")
public class DataController {
    @RequestMapping("compute")
    public String compute() {
        Byte[] b = new Byte[1024*1024];
        return "success";
    }
}
  • JVM参数
  • 调整参数一
  • 参数 -Xms64m -Xmx64m
  • 性能指标


f054390041cb4bd49821ae89ddbe8279.png

  • 调整参数二
  • 参数 -Xms640m -Xmx640m
  • 性能指标

29d27b4142e047368d29e2434b3a7b86.png

6.JDK8之后的方法区实现和元空间的联系

(1)什么是方法区和元空间

  • 【方法区】是JVM中用来存储类的元数据信息的区域,包括类的结构、方法、字段信息等,Java堆类似各个线程共享的内存区域
  • 元空间、永久代是方法区具体的落地实现
  • java8之前是称为永久代(PermGen),java8后引入的一个新概念【元空间】用于替代旧版JVM中的永久代(PermGen)
  • 方法区和永久代以及元空间的关系很像 Java 中接口和类的关系
  • 类实现了接口,类就可以看作是永久代和元空间,接口可以看作是方法区
  • 永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现便成为元空间


204177e1bfbb45c49bb470d7e3d1e789.png

  • 元空间的大小是动态的,可以根据需要进行自动扩展,如果元空间不足,JVM会抛出 OutOfMemoryError : Metaspace
  • 元空间大小配置
  • -XX:MetaspaceSize
  • 用来设置元空间初始大小的参数,它的默认值是21 MB
  • -XX:MaxMetaspaceSize
  • 用来设置元空间最大大小的参数,它的默认值是-1 即不限制,使用的是本地内存,不像旧版的永久代是堆内存
  • 如果不限制元空间的大小,可能会导致元空间占用过多的内存,从而引起内存溢出
  • 系统参数查看
  • 这两个参数的单位是字节(B),可以使用K、M、G等后缀来表示更大的单位
public class HeapDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("测试元空间进程");
        Thread.sleep(10000000);
    }
}
  • 查看命令
jps #查看进程号
jinfo -flag MetaspaceSize 进程号  #查看Metaspace分配内存空间 
jinfo -flag MaxMetaspaceSize 进程号 #查看Metaspace最大空间

5719073af21a4cacb943d4a7120efd6b.png

  • 调整
-XX:MetaspaceSize=126m -XX:MaxMetaspaceSize=524m

8c1c5d2bee694fcdb70c59510929fafa.png


相关文章
|
8天前
|
存储 Java
深入理解Java虚拟机:JVM内存模型
【4月更文挑战第30天】本文将详细解析Java虚拟机(JVM)的内存模型,包括堆、栈、方法区等部分,并探讨它们在Java程序运行过程中的作用。通过对JVM内存模型的深入理解,可以帮助我们更好地编写高效的Java代码,避免内存溢出等问题。
|
1天前
|
Java 编译器 对象存储
java一分钟之Java入门:认识JDK与JVM
【5月更文挑战第7天】本文介绍了Java编程的基础——JDK和JVM。JDK是包含编译器、运行时环境、类库等的开发工具包,而JVM是Java平台的核心,负责执行字节码并实现跨平台运行。常见问题包括版本不匹配、环境变量配置错误、内存溢出和线程死锁。解决办法包括选择合适JDK版本、正确配置环境变量、调整JVM内存参数和避免线程死锁。通过代码示例展示了JVM内存管理和基本Java程序结构,帮助初学者更好地理解JDK和JVM在Java编程中的作用。
12 0
|
1天前
|
存储 监控 安全
JVM工作原理与实战(十六):运行时数据区-Java虚拟机栈
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、Java虚拟机栈等内容。
|
2天前
|
Java
Java中的多线程编程:基础知识与实战技巧
【5月更文挑战第6天】多线程编程是Java中的一个重要特性,它允许我们在一个程序中同时执行多个任务。本文将介绍Java多线程的基础知识,包括线程的创建、启动、同步和通信,以及如何在Java中实现多线程编程。通过实例代码和解析,帮助读者深入理解Java多线程编程的概念和应用。
|
2天前
|
存储 缓存 安全
【 Java中String源码分析(JVM视角你不来看看?】
【 Java中String源码分析(JVM视角你不来看看?】
8 0
|
6天前
|
存储 Java 数据格式
Java实战:轻松掌握文件重命名与路径提取技巧
Java实战:轻松掌握文件重命名与路径提取技巧
15 0
|
8天前
|
Java
jvm配置参数,查看大对象直接分配到老年代
jvm配置参数,查看大对象直接分配到老年代
|
8天前
|
设计模式 算法 安全
Java多线程编程实战:从入门到精通
【4月更文挑战第30天】本文介绍了Java多线程编程的基础,包括线程概念、创建线程(继承`Thread`或实现`Runnable`)、线程生命周期。还讨论了线程同步与锁(同步代码块、`ReentrantLock`)、线程间通信(等待/通知、并发集合)以及实战技巧,如使用线程池、线程安全设计模式和避免死锁。性能优化方面,建议减少锁粒度和使用非阻塞算法。理解这些概念和技术对于编写高效、可靠的多线程程序至关重要。
|
1天前
|
Arthas 监控 Java
JVM工作原理与实战(三十一):诊断内存泄漏的原因
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了诊断内存溢出的原因、MAT内存泄漏检测的原理等内容。
|
1天前
|
存储 Arthas 监控
JVM工作原理与实战(三十):堆内存状况的对比分析
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了堆内存状况的对比分析、产生内存溢出的原因等内容。