JVM内存模型深度剖析和优化

本文详细解析了Java的跨平台性实现机制,重点探讨了JVM内存模型,包括类加载、字节码执行、运行时数据区(堆、方法区、栈、本地方法栈和程序计数器)、内存管理(如GC、STW和直接内存使用),并提供了JVM调优建议和常见问题解答。
摘要由CSDN通过智能技术生成

JVM内存模型深度剖析和优化

Java语言的跨平台性

Java语言的跨平台特性.png

  • 问题: Java语言的跨平台性是如何做到的
    • 通过不同操作系统平台的JVM版本,Linux和Windows的JVM版本各不相同
    • Java是跨平台解释性语言,可以在不同的操作系统运行,JVM从软件层面屏蔽不同操作系统在底层硬件和指令的差别

JVM内存模型

详细链接: https://www.processon.com/view/link/61e31579f346fb06cb9afec9

JVM内存模型.png

  • JVM的组成部分主要分为两个子系统和两个组件
    • 两个子系统 就是 类加载子系统 和 字节码执行引擎
      • 类加载子系统 主要是负责将字节码文件加载到JVM内存中去
      • 字节码执行引擎 主要是负责GC、执行字节码文件中的指令以及修改程序计数器中的值等
    • 两个组件 就是 本地接口 和 运行时数据区
      • 本地接口 主要是与本地库打交道,都知道JVM是C++写的,所以需要一些本地库的支持
      • 运行时数据区 就是我们常说的JVM内存,主要分为线程共享和线程独享两大块
        • 线程共享 有 堆 和 方法区
            • 一般new出来的对象都会存放在堆中
          • 方法区(元空间)
            • 方法区在JDK 7时处于堆中,在JDK 8中,方法区叫元空间,从堆中移除而放到直接内存中,主要是因为直接内存对IO操作具有更高的性能并且减少中间步骤的开销(虚拟内存到直接内存的重复开销)
        • 线程独享 有 栈、本地方法栈 和程序计数器
            • 栈主要是存放栈帧的,一个方法对应一个栈帧,栈帧主要分为局部变量表、操作数栈、方法出口、动态链接
              • 局部变量表
                • 局部变量表 类似于一个table,存放编译期间的变量或对象的内存地址
                  • 在编译期间,this会作为隐式参数放在局部变量表的第一位
              • 操作数栈
                • 操作数栈 主要是用来进行一些操作数运算,运算完之后再赋值到局部变量表
              • 方法出口
                • 方法出口 就是通过它进行定位方法被调用的位置
              • 动态链接
                • 动态链接 就是引用类型变量与堆中实际对象的关联关系,主要是用来定位堆中实际对象的
          • 本地方法栈 与 栈基本一致,只不过是用来处理本地方法
          • 程序计数器 相当于代码执行位置的标识
  • 整体流程就是 类加载子系统会将字节码文件加载到JVM内存中,而字节码文件不能直接被操作系统识别,所以需要通过字节码执行引擎去解析成操作系统能识别的指令,然后将指令交给CPU去执行,而这个过程需要本地接口与本地库的交互

JVM参数设置

JVM内存参数设置.png

  • 分析: 方法区如果内存使用达到21M(默认21M),也会触发FullGC,FullGC不但会回收堆也会回收方法区

  • 推荐: JVM调参

    java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar a.jar
    
  • 方法区的参数

    • -XX:MaxMetaspaceSize: 设置元空间最大值,默认-1(表示不受限制),会进行动态扩容,只受限于本地内存大小
    • -XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整
      • 如果释放了大量的空间, 就适当降低该值
      • 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值,这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量
  • 堆参数

    • -Xms: 堆空间初始大小
    • -Xmx: 堆最大空间
    • -Xmn: 年轻代大小
    • -Xss: 栈空间大小
    • -XX:NewRatio: 默认2表示年轻代占老年代的1/2,占堆的1/3
    • -XX:SurvivorRatio: 默认8表示一个Survivor区占Eden区的1/8,占堆的1/10
  • 调优: 在设置JVM参数时,建议将方法区也设置一个值比如256M,避免在项目启动时,因为方法区太小而导致频繁FullGC

常见问题

什么样的对象会被移动到老年代

  • 长期存活的对象,比如静态变量引用对象、对象池、缓存对象、Spring容器中的Bean对象等

MinorGC和FullGC都会触发STW吗

  • 都会,但MinorGC触发STW时间比较短,用户几乎无感知,但FullGC触发时间比较长

为什么要设置STW机制

  • 什么是STW

    • STW就是Stop The World,停止所有用户线程。字节码执行引擎会开启后台线程去进行垃圾回收,就比如电商系统,用户发起下单请求对应的后端会有用户线程进行处理,而相对应的会产生大量的对象,需要及时的进行垃圾回收,这时候会进行STW,对于用户的体验就是卡顿一下,如果STW时间过长对于用户的体验是非常不好的
  • 当通过gcroots去找可回收的对象时,假如当前确认A对象此时没有被引用着,如果不停止所有用户线程,可能会出现有其他线程再次引用A对象,但gcroots不知道A对象又被引用了,可能就直接把该对象回收掉了,造成这条引用断裂,这样会出大问题,为了避免这种情况的产生,JVM干脆停止所有用户线程去进行回收

  • 可以举个例子,在使用serial和parNew进行垃圾回收时,因为采用的是复制算法,如果不暂停所有用户线程,那么复制到Survivor区可能有存活的对象和垃圾对象,这样肯定是不行的

什么时候会报错StackOverflowError

  • 比如当递归调用方法的时候很有可能出现该错误,因为线程栈被栈帧放满
  • -Xss设置越小,说明一个线程栈里能分配的栈帧也就越小,但是对JVM整体来说能开启的线程数也就越多,为什么这么说呢
    • 因为方法区和栈都是直接使用操作系统的直接内存的,栈内存越小,理论上操作系统就能开更多的线程

JVM优化原则是什么

  • 尽可能让对象都在年轻代里被分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免年轻代频繁的进行垃圾回收

解释下Java里面的直接内存

  • 直接内存有一种叫法堆外内存
  • 直接内存指的是Java应用程序直接从操作系统申请到的内存,运行时数据区的内存都是虚拟的内存

为什么JDK8将方法区从堆上移动到堆外内存

  • 因为直接使用直接内存,IO操作上具有更高的性能,跟零拷贝的原理有点雷同

有哪些方式可以直接使用直接内存

  • Java的Unsafe类,做了一些本地内存的操作
  • Netty的直接内存(Direct Memory),底层会调用操作系统的malloc函数
  • JNI或者JNA程序,直接操纵了本地内存,比如一些加密库
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枫吹过的柚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值