【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


相关文章
|
2月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
63 0
|
23天前
|
存储 监控 算法
Java JVM 面试题
Java JVM(虚拟机)相关基础面试题
|
2月前
|
NoSQL Java Redis
秒杀抢购场景下实战JVM级别锁与分布式锁
在电商系统中,秒杀抢购活动是一种常见的营销手段。它通过设定极低的价格和有限的商品数量,吸引大量用户在特定时间点抢购,从而迅速增加销量、提升品牌曝光度和用户活跃度。然而,这种活动也对系统的性能和稳定性提出了极高的要求。特别是在秒杀开始的瞬间,系统需要处理海量的并发请求,同时确保数据的准确性和一致性。 为了解决这些问题,系统开发者们引入了锁机制。锁机制是一种用于控制对共享资源的并发访问的技术,它能够确保在同一时间只有一个进程或线程能够操作某个资源,从而避免数据不一致或冲突。在秒杀抢购场景下,锁机制显得尤为重要,它能够保证商品库存的扣减操作是原子性的,避免出现超卖或数据不一致的情况。
70 10
|
2月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
3月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
71 1
|
3月前
|
Oracle 安全 Java
深入理解Java生态:JDK与JVM的区分与协作
Java作为一种广泛使用的编程语言,其生态中有两个核心组件:JDK(Java Development Kit)和JVM(Java Virtual Machine)。本文将深入探讨这两个组件的区别、联系以及它们在Java开发和运行中的作用。
139 1
|
27天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
85 17
|
2月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
23天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题