二、JVM内存模型

1、Java语言跨平台特性

  • java程序主要通过JVM来实现跨平台的,JVM编译器将Java源代码文件编译成字节码文件(一次编译,随处运行),然后不同的操作系统生成的机器码不同,但是JVM运行是相同的,JVM解释器将字节码文件转换为机器可执行的二进制机器码

2、JVM内存模型

  • jvm包含两个子系统为类装载子系统字节码执行引擎。两个组件为运行时数据区本地接口
    • 类装载子系统:根据给定的全限定名类名(如:java.lang.Object来装载Class文件到Runtimedata area中的Metaspace。
      字节码执行引擎:执行classes中的指令。
      本地接口:与本地方法库交互,是其它编程语言交互的接口。
      运行时数据区:这就是我们常说的JVM的内存。
  • 一个Java程序的执行过程:
    1. 首先通过编译器把 Java 代码转换成字节码。
    2. 类装载子系统(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行。
    3. 字节码执行引擎(Execution Engine),将字节码翻译成底层系统指令。
    4. 由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

示例

  • //上图示例中的代码:
    public class Math {public static int initData = 123;public static User user = new User();public int compute() {int a = 1;int b = 2;int c = (a + b) * 10;return c;}public static void main(String[] args) {Math math = new Math();math.compute();System.out.println("end");}
    }
    
  • 通过类加载器将类(类元信息)加载到方法区中,compute() 方法也被加载到方法区中;java代码在执行 math.compute() 的时候,通过 math 对象的对象头中的头指针找到存储在方法区中的 compute() 方法的指令码的入口地址,然后将这个入口地址放到栈中的动态链接内存区域中。

    1. 类装载子系统(ClassLoader),将class文件经过加载、验证、准备、解析、初始化后生产类元信息放入到方法区中(类装载子系统主要将类装载到方法区);常量池是在方法区中的。
    2. 栈里面的变量引用指向堆中的对象;基本类型的局部变量放到对应的栈中,对象放到堆中。
    3. 对象(在堆中)的对象头中有一个指针,指向的是这个对象所属类的类元信息(方法区中)。
    4. 静态变量是存在方法区中的,其引用指向堆中的对象。

3、JVM的内存区域

  • JVM内存模型主要包括几大模块:程序计数器(Program Counter Register)、虚拟机栈(VM stack)、堆(Heap)、元空间(Metaspace,JDK8后由方法区改为元空间)、本地方法栈(Native Method Stack)。
  • 其中堆、元空间是线程共享的,程序计数器、虚拟机栈、本地方法栈是线程私有的。

(1)程序计数器(Program Counter Register)

  • 线程私有的。
  • 存储指指向当前线程执行到的指令,由字节码执行引擎读取下一条指令。
  • 字节码执行引擎通过改变程序计数器来依次读取指令,从⽽实现代码的流程控制。
  • 在多线程的情况下,程序计数器⽤于记录当前线程执⾏的位置,从⽽当线程被切换回来的时候能够知道该线程上次运⾏到哪里。
  • 为了线程切换后能恢复到正确的执⾏位置,每条线程都需要有⼀个独⽴的程序计数器,各线程之间计数器互不影响,独⽴存储

(2)虚拟机栈(VM Stack)

  • 线程私有的
  • 虚拟机栈是由一个个栈帧组成,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 局部变量表:存放局部变量。
    • 操作数栈:具体的值,做操作的时候用它来作临时空间,执行完后结果会放到局部变量表中。
    • 动态链接:动态链接会将方法的符号引用改为指向内存地址的直接引用,这里是用来存地址的值的。
    • 方法出口:内层方法返回外层方法的时候,需要知道外层方法执行到第几行了,方法出口就是存储外层方法执行的位置。
  • 存放基本类型的变量、实例方法、引用类型变量都是在函数的栈内存中分配;
  • 每一次函数调用都会有一个对应的栈帧被压入虚拟机栈,每一个函数调用结束后,都会有一个栈帧被弹出。

(3)本地方法栈(Native Method Stack)

  • 线程私有的。
  • 本地方法栈和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行Java方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native方法服务。

(4)堆(Heap)

  • 线程共享的。
  • 创建的对象和数组都保存在 Java 堆内存中。
  • Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap
  • 包含年轻代与老年代。年轻代分为Eden区与s0区,s1区。默认年轻代占堆的1/3,老年代2/3。Eden : s0 : s1=8 : 1 : 1。

(5)元空间(Program Counter Register)

  • 线程共享的。
  • 通常用来储存装载的类的元结构信息、常量、静态变量
  • 如果静态变量为对象类型,则存储一个指向堆的内存地址。

4、堆的GC回收机制

  • GC 一般分为 Minor GC 和 Full GC。Minor GC会在年轻代中触发,而Full GC是在老年代中触发。
  • GC回收的过程:
    1. 当new 一个对象时,该对象会被放入Eden区,创建的对象越来越多,Eden区快满的时候会触发一次轻量级垃圾回收(Minor GC),会从gc root开始查找,将没有对象引用的对象给回收掉。
    2. 未被回收的对象被放入S0区,Eden被清空,这些还存活的对象的分代年龄会+1。
    3. 继续 new 对象,当Eden区再次快放满的时候,又会触发一次垃圾回收机制(Minor GC),将Eden区 和 S0区从gc root开始查找,将没有对象引用的对象给回收掉。将未被回收的对象一起放入到S1区,S1区 和 Eden区 被清空,这些还存活的对象的分代年龄会+1。
    4. 之后Eden区再次快放满的时候,又会触发一次垃圾回收机制(Minor GC),将Eden区 和 S1区从gc root开始查找,将没有对象引用的对象给回收掉。将未被回收的对象一起放入到S0区,S1区 和 Eden区 被清空,这些还存活的对象的分代年龄会+1。
    5. 对象的对象头中存储了分代年龄的存储信息,当这个对象的分代年龄达到了阈值(默认15),还没有被垃圾机制回收,则会将这些对象放入到老年代。
    6. 以此类推,一再触发Minor GC,直到老年区存满,则会触发一次重量级垃圾回收机制(Full GC)。Full GC会回收整个堆和方法区。如果full gc后再存对象还是存不下,则会触发OOM异常。
  • 在GC回收的时候会触发STW(stop the work),STW会暂停当前运行的线程,这个时候对用户来说会有明显的卡顿,因此JVM调优的目的就是减少STW的次数。

5、为什么要STW

  • 因为每次gc的时候从gc root去查找对象是否存活的计算十分复杂,耗时很长。如果不暂停正在运行的线程,会出现一个对象可能在gc root检查的时候是存活对象,然后检查完后,移动到s0区 之后对象执行完毕,被释放掉了。这个时候就浪费了之前的gc root检查。会导致当前的gc root检查出来的结果不正确。

6、JVM内存参数设置

  • Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):

    • java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar
      
    • -Xss:每个线程的栈大小

    • -Xms:设置堆的初始可用大小,默认物理内存的1/64

    • -Xmx:设置堆的最大可用大小,默认物理内存的1/4

    • -Xmn:新生代大小

    • -XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。

    • -XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。

    • 关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N

      • -XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制,元空间最大的大小是系统内存的大小,元空间一直扩大,虚拟机可能会消耗完所有的可用系统内存。
      • -XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小),如果不指定元空间的大小, 64位系统默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。
    • -XX:PermSize代表元空间(永久代)的初始容量,JDK8及以后已经没有了

      • 由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。
  • StackOverflowError示例:

    • // JVM设置  -Xss128k(默认1M)
      public class StackOverflowTest {static int count = 0;static void redo() {count++;redo();}public static void main(String[] args) {try {redo();} catch (Throwable t) {t.printStackTrace();System.out.println(count);}}
      }运行结果:
      java.lang.StackOverflowErrorat com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:12)at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13)at com.tuling.jvm.StackOverflowTest.redo(StackOverflowTest.java:13)......
      
    • 结论:-Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多。

7、JVM内存参数大小该如何设置?

  • JVM参数大小设置并没有固定标准,需要根据实际项目情况分析,下面提供一个案例

  • 日均百万级订单交易系统设置JVM参数案例

    • 我们初次给服务器的内存参数设置如下

    • ‐Xms3072M ‐Xmx3072M ‐Xss1M -XX:MetaspaceSize=512M ‐XX:MaxMetaspaceSize=512M# ‐Xms3072M 表示堆初始化内存为3G
      # -Xmx3072M 表示堆最大内存为3G
      # -Xss1M 表示每个线程内存为1M
      # -XX:MetaspaceSize=512M 表示元空间初始化内存为512M
      # -XX:MaxMetaspaceSize=512M 表示元空间最大内存为512M#因为堆的新生代:老年代比例大约是1:2, 即新生代为1G、老年代为2G。 在新生代中Eden区、S0区、S1区比例大约是8:1:1,即Eden区为800M,S0区为100M,S1区为100M。
      
    • 由上图可知,生成对象首先会在Eden区,如果每秒产生60M对象,那么14秒后Eden区就会被占满,此时会触发Minor GC,第14秒产生的对象因为可能还会被引用所以没有被回收,根据垃圾回收机制存活的对象会被放到S0区,但是根据动态对象年龄判断原则,这60M对象同龄而且总和大于S0区的50%,那么这些对象都会被挪到老年代。14秒就会产生一个60M对象到老年代且1秒后就会变成垃圾,大概8分钟老年代的内存就会被沾满,就会触发Full GC。因为Full GC是重量级回收,如果每8分钟就要执行一次Full GC会导致系统性能很低。所以合理设置内存参数可以达到几乎不产生Full GC的效果。

    • 我们再次给服务器的内存参数设置如下

    • -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M# ‐Xms3072M 表示堆初始化内存为3G
      # -Xmx3072M 表示堆最大内存为3G
      # -Xmn2048M 表示新生代大小为2G
      # -Xss1M 表示每个线程内存为1M
      # -XX:MetaspaceSize=512M 表示元空间初始化内存为512M
      # -XX:MaxMetaspaceSize=512M 表示元空间最大内存为512M#此时设置新生代为2G,所以老年代为1G。 在新生代中Eden区、S0区、S1区比例大约是8:1:1,即Eden区大约为1638M,S0区为205M,S1区为205M。
      
    • 在重新设置了服务器内存参数后,我们再次分析:如果每秒产生60M对象,则需要大约27秒Eden区会被占满,此时触发Minor GC,第14秒产生的对象因为可能还会被引用所以没有被回收,根据垃圾回收机制存活的对象会被放到S0区,此时由于这60M对象同龄总和没有达到S0区的50%,不会直接进入老年代,因此在下一次Minor GC就会被回收。

  • 合理设置内存参数可以达到几乎不产生Full GC的效果。

  • JVM优化核心思想:就是尽可能让对象都在新生代里分配和回收,尽量别 让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。

二、JVM内存模型及内存参数设置相关推荐

  1. JVM内存模型、相关参数设置与命令查看

    JVM内存模型.相关参数设置与命令查看 JVM内存模型,你看这一篇就够了 - 知乎 (zhihu.com) Java虚拟机-Java8内存模型JVM(整理版) - 牧梦者 - 博客园 (cnblogs ...

  2. jvm调优二:jvm内存模型剖析和参数设置

    简述 在开始学习java的时候,我们知道java是一个跨平台的语言,为什么java能够跨平台,主要是因为jvm屏蔽了操作系统底层的差异.下面重点来研究下jvm.本次研究的jvm都是jdk1.8的jvm ...

  3. JVM内功心法-JVM内存模型之内存区域

    JVM内功心法-JVM内存模型之内存区域 程序计数器(线程隔离) 程序计数器(Program Counter Register):也叫PC寄存器,是一块较小的内存空间,它可以看做是当前线程所执行的字节 ...

  4. java内存模型和内存结构_Java内存模型和优化

    java内存模型和内存结构 总览 许多多线程代码开发人员都熟悉这样的想法,即不同的线程可以对持有的值有不同的看法,这不是唯一的原因,即如果线程不安全,它可能不会看到更改. JIT本身可以发挥作用. 为 ...

  5. java 线程内存模型_JAVA内存模型与线程

    概述 由于计算机的运算速度和它的存储和通讯子系统的速度差距巨大,大部分时间都花在IO,网络和数据库上.为了压榨CPU的运算能力,需要并发.另外,优秀的并发程序对于提高服务器的TPS有重要的意义. 硬件 ...

  6. 内存模型 linux,内存模型 - STM32F4 编程手册学习_Linux编程_Linux公社-Linux系统门户网站...

    STM32F4编程手册学习2_内存模型 1. 内存映射 MCU将资源映射到一段固定的4GB可寻址内存上,如下图所示. 内存映射将内存分为几块区域,每一块区域都有一个定义的内存类型,一些区域还有一些附加 ...

  7. R语言epiDisplay包的kap函数计算配对列联表的计算一致性的比例以及Kappa统计量的值(总一致性、期望一致性)、使用xtabs函数生成二维列联表、使用wttable参数设置权重表参数为w2

    R语言使用epiDisplay包的kap函数计算配对列联表的计算一致性的比例以及Kappa统计量的值(总一致性.期望一致性).使用xtabs函数生成二维列联表.使用wttable参数设置权重表参数为w ...

  8. 聊聊内存模型和内存序

    本文始发于公众号[高性能架构探索],本公众号致力于分享干货.硬货以及工作上的bug分析,欢迎关注.回复[pdf]免费获取计算机经典书籍 你好,我是雨乐! 最近群里聊到了Memory Order相关知识 ...

  9. java内存分配空间大小,JVM内存模型及内存分配过程

    一.JVM内存模型 JVM主要管理两种类型内存:堆(Heap)和非堆(Permanent区域). 1.Heap是运行时数据区域,所有类实例和数组的内存均从此处分配.Heap区分两大块,一块是 Youn ...

最新文章

  1. 程序员经常去的 14 个顶级开发者社区(转)
  2. Java 7在整数中出现的次数
  3. Python——OpenCV(opencv-python库)调用摄像头
  4. java 如何循环执行一个对象_一个Java对象到底有多大?
  5. android按钮在容器下方,使用flex布局解决安卓手机上固定在底部的按钮,在键盘弹起后挡住input输入框的问题...
  6. 白帽子技术分析会话劫持实战讲解
  7. maven安装以及常用配置,idea如何配置maven
  8. 哈密顿图和欧拉图知识小结
  9. 2020腾讯软件测试实习面经
  10. ❤️【图文并茂】Chrome浏览器(油猴子)插件安装使用教程❤️
  11. Java从入门到精通章节练习题——第三章
  12. linux双核cpu调试软死锁问题,记一次linux通过jstack定位CPU使用过高问题或排查线上死锁问题...
  13. java爬虫爬取京东_java爬虫练习|爬取京东上的手机商品数据
  14. 阿里云磁盘异常爆满的原因排查及解决方法
  15. win10计算机管理 用户,Win10专业版系统管理员帐户的开启设置方法
  16. vivo 调用链 Agent 原理及实践
  17. 云之讯php,云之讯短信开发
  18. 比 OpenAI 更好!!谷歌发布 20 亿参数通用语音模型——USM
  19. 数据结构与算法-查找和排序
  20. IDEA初学者保存就格式化代码插件save actions

热门文章

  1. mediarecorder自制录相机花屏问题 微信录制视频花屏
  2. CVPR 2019 目标检测论文汇总
  3. 高博第9讲project opencv viz模块
  4. 分享微信答题活动制作步骤_制作微信答题活动要注意什么
  5. 梁辉老师.狼性营销实战训练专家
  6. 星驰云算GOSTAR,携手Swarm共建Web 3.0时代
  7. 软件测试知识点(持续更新)
  8. 前端的小激动:Nodejs写简单接口教程
  9. SqlServer2012卸载并重新安装成功完整过程!真的超级激动...
  10. 【巧用自带清空内存命令 一键释放内存资源】