基本概念

——JVM位置

JVM运行在操作系统之上的,与硬件没有直接的交互

——体系结构

1. 类装载器ClassLoader

  • 负责加载class文件,class文件在文件开头有特定的文件标示(cafe babe),将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定

  • 并不是所有后缀为.class的文件都能被加载,只有在文件开头为cafe babe的文件才是唯一认证

  • 虚拟机自带的加载器

    • 启动类加载器(BootStrap):C++编写,出厂自带,java环境变量配置中的jre的bin目录中的所有.class文件,默认加载最早的类文件(ArrayList、Object)等,导包前缀为java
    • 扩展类加载器(Extension):java编写,后续版本迭代的类加载,导包前缀为javax
    • 应用程序类加载器(AppClassLoader):java也叫系统类加载器,加载当前应用的classpath的所有类,自己创建定义的类,应用时需要被加载
    • 启动类加载器 > 扩展类加载器 > 应用程序加载器(可通过.getclass().getClassLoader().getParent()验证 )
  • jre / bin / rt.jar(runtime.jar)/ sun.misc.Launcher是java虚拟机的入口

双亲委派机制

  • 有事往上桶(沙箱安全机制)
  • 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
  • 采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。
  • 类加载从自顶向下加载,当加载一个用户定义的对象时(应用程序加载器),先找启动类加载器,没找到再找扩展类加载器,没找到再找应用程序加载器,还没有的话报ClassNotFound异常

2. 运行时数据区

Native Interface本地接口(本地方法栈)

  • 本地接口的作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++程序,Java 诞生的时候是 C/C++横行的时候,要想立足,必须有调用 C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是 Native Method Stack中登记 native方法,在Execution Engine 执行时加载native libraies
  • native关键字:存在于(native)本地方法栈,调用底层的C语言函数库

PC寄存器

  • 每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也就是将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,可以忽略不计
  • 这块内存区域很小,它是当前线程所要执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来读取下一条需要执行的字节码指令
  • 如果执行的是一个Native方法,那这个计数器就为空
  • 用以完成分支、循环、跳转、异常处理、线程恢复等基础功能。不会发生OOM(内存溢出)错误

方法区

  • 供每个线程共享的运行时内存区域
  • 存储了每个类的结构信息,运行时的常量池,字段,方法数据,构造方法,普通方法
  • 实例变量存储在堆内存中,和方法区无关

  • 包含 局部变量表操作数栈动态链接方法出口
  • 栈也叫栈内存,栈管运行,堆管内存,先进后出
  • 在创建线程时创建,它的生命周期跟随线程的生命周期,线程结束栈内存释放
  • 对于栈来说不存在垃圾回收,线程私有,只要线程结束该栈就结束
  • 存储:8种基本数据类型的变量+对象的引用变量+实例方法都是在栈内存中分配
  • 每个方法执行的过程中都会创建一个栈帧(在JVM中叫栈帧,JVM外叫方法),用于存储局部变量表,操作数栈,动态链接,方法出口信息;栈的大小通常在256k~756k左右

堆(重点)

  • 一个JVM只存在一个堆内存,堆内存的大小是可以调节的;类加载器读取了类文件后,需要把类、方法、常量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行

  • 堆内存逻辑上分为三部分(物理上只有新生代+老年代两部分),新生代+老年代+元空间(java1.7称为永久代,1.8后称为元空间)

  • 元空间和永久代最本质的区别为:永久代使用的是JVM的堆内存,元空间使用的是本机的物理内存;因此,元空间的大小仅受本地内存限制

  • 当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC,轻量GC也叫YGC),将伊甸园区中的不再被其他对象所引用的对象进行销毁

  • 若养老区也满了,那么这个时候将产生MajorGC(FullGC),进行养老区的内存清理。若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”(堆内存溢出)

  • 问题(重点):如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够,原因以下:

    • Java虚拟机的堆内存设置的不够,可通过-Xms、-Xmx来调整
    • 代码中创建了大量的大对象,并且长时间不能被垃圾收集器收集(大对象一直处于被引用的状态)
  • 从GC角度细分

    • YGC(轻GC)的过程:复制——》清空——》互换
    1. eden、SurvivorFrom 复制到 SurvivorTo,年龄+1
      首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到S0urvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1
    2. 清空 eden、SurvivorFrom
      然后,清空Eden和SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to区
    3. SurvivorTo和 SurvivorFrom 互换
      最后,SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区。部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代

内存管理

  • 不同对象的生命周期不同,98%的对象都是临时对象
  • 实际上,方法区和堆一样,是各个线程共享的内存区域,用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码;JVM规范将方法区描述为堆的一个逻辑部分,但它有个别名叫非堆,目的就是要和堆分开
  • 大多习惯称方法区就是永久代,严格本质上两者有所不同,方法区是一个规范、概念,而永久代是方法区的一个实现
  • 永久存储区是一个常驻内存区域,存放JDK自身携带的Class,Interface的元数据,存储的是运行环境必须的类信息,加载的就是rt.jar包所有的对象,被装载进此区域的数据是不会被垃圾回收器回收掉,关闭JVM才会释放此区域所占用的内存

GC Roots

  • GC Root 表示当前存活的对象(包括 局部变量表静态变量常量本地方法栈 中的对象)
  • JVM 中判断一个对象是否标记为可回收的对象是根据 可达性分析算法(以 GC Roots 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象)

3.执行引擎

——性能调优

STW:Stop-The-World

  • 是在垃圾回收算法的执行过程中,将 JVM内存冻结、应用程序停顿 的一种状态
  • 在STW 状态下,JAVA的所有线程都是停⽌执⾏的,GC线程除外
  • STW是不可避免的,垃圾回收算法执⾏一定会出现STW,我们要做的只是减少停顿的时间
  • 减少STW(暂停)降低GC垃圾回收的频率 是调优的重点
  • 如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证
  • 如果系统卡顿很明显,大概率就是频繁执行GC垃圾回收,频繁进入STW状态产生停顿的缘故

常见诊断工具

  • Java Visual VM(JVisual VM),可安装插件(Visual GC)
  • 阿里调优工具 Arthas

调优命令:可在运行前的VM -options中进行调整JVM的内存大小
(-Xms1024m -Xmx1024m -XX:+PrintGCDetails)

  • -Xms:设置初始分配大小,默认为物理内存的1/64
  • -Xmx:最大分配内存,默认为物理内存的1/4
  • -XX:+PrintGCDetails:输出详细的GC日志

基本原则

  • 设置的初始值应该和最大值一致,避免内存忽高忽低,产生停顿,而且GC与最大值无关
  • 减少GC回收次数,因为GC回收本身会影响程序的效率

GC

次数上频繁收集新生代,较少收集老年代,基本不动元空间

四大算法

  1. 引用计数法:
    缺点:每次对对象赋值时要维护计数器,较难处理循环利用,一般不采用该方式
  2. 复制算法(复制——交换——清空,发生在新生代)
    Eden:From:To为8:1:1的原因,就是From到To的内存大小一致才可实现完全复制,不产生内存碎片
    缺点:当对象的存活率较高时,极大的占用内存,存活率较低使用
  3. 标记清除(发生在老年代):
    为了弥补复制算法浪费空间的缺点,先标记,再清除,节约空间;
    缺点:效率低,清除的地方会产生内存碎片
  4. 标记整理(发生在老年代,与标记清除混合使用):
    弥补标记清除所产生的内存碎片,标记的对象将会被整理,未标记的将会被清除,减少产生的内存碎片
    缺点:耗时长,效率低

补充

  • 内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)
  • 内存整齐度:复制算法=标记整理算法>标记清除算法
  • 内存利用率:标记整理算法=标记清除算法>复制算法
  • 没有最好的算法,只有最合适的算法,需要通过分代来选择对应的回收算法

——常用参数

  • -Xms:初始堆内存大小,默认物理内存64/1
    -Xms = -XX:InitialHeapSize
  • -Xmx:最大堆内存,默认物理内存4/1
    -Xmx = -XX:MaxHeapSize
  • -Xss:栈内存大小
    -Xss = -XX:ThreadStackSize
    设置单个线程栈大小,一般默认512~1024kb,单个线程栈大小跟操作系统和JDK版本都有关系
  • -Xmn:年轻代大小
  • -XX:MetaspaceSize:元空间大小
    元空间本质跟永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间并不在虚拟机中,而是使用本机内存。因此,元空间大小仅受本地内存限制。
  • -XX:+PrintGCDetails:打印GC详细日志信息
  • -XX:SurvivorRatio:幸存者比例设置
  • -XX:NewRatio:新生代比例设置
  • -XX:MaxTenuringThreshold:进入老年代阈值设置

一台 java 服务器可以跑多少个线程?

  • 公式:线程数量=(机器本身可用内存 - JVM分配的堆内存)/ Xss的值
  • (不考虑系统限制)假设我们的容器本身大小是8G,堆大小是4096M,走 -Xss 默认值,可以得出最大线程数量:4096个
  • 结论1:jvm堆越大,系统创建的线程数量越小
    结论2:当-Xss的值越小,可生成线程数量越多

JVM底层原理分析 + 性能调优相关推荐

  1. elasticsearch原理_ElasticSearch读写底层原理及性能调优

    ES写入/查询底层原理 1. Elasticsearch写入数据流程 客户端随机选择一个ES集群中的节点,发送POST/PUT请求,被选择的节点为协调节点(coordinating node) 协调节 ...

  2. elasticsearch最大节点数_ElasticSearch读写底层原理及性能调优

    ES写入/查询底层原理 1. Elasticsearch写入数据流程 客户端随机选择一个ES集群中的节点,发送POST/PUT请求,被选择的节点为协调节点(coordinating node) 协调节 ...

  3. ElasticSearch读写底层原理及性能调优

    ##一,读写底层原理 Elasticsearch写人数据的过程 1)客户端选择一个node发送请求过去,这个node就是coordinating node(协调节点) 2)coordinating n ...

  4. jvm原理及性能调优系列(jvm调优)

    jvm原理及性能调优系列(jvm调优) JVM设置: 1.设置合适的最大堆内存(新生代和老生代的最大和值)和最小堆内存(jvm启动时占用的操作系统内存大小),及设置好堆的比例分配. 2.设置合适的新生 ...

  5. JVM内存模型和性能调优:系列文章 - 导读

    0.JVM课程总体介绍 学习 Java 虚拟机能深入地理解 Java 这门语言,想要深入学习java的各种细节,很多时候你要深入到字节码层次去分析,你才能得到准确的结论,通过学习JVM你了解JVM历史 ...

  6. gateway 内存溢出问题_带你学习jvm java虚拟机 arthas/性能调优/故障排除/gc回收/内存溢出等...

    学完本课程,您将掌握: 内存溢出问题实战 CPU飙升问题实战 阿里巴巴Arthas在线诊断 Class字节详细拆解 手写类加载器.四种类加载器.双亲委托模型 对象创建.存储.访问.加载解析 性能调优. ...

  7. mysql性能调优与架构设计_了解架构设计远远不够!一文拆解 Tomcat 高并发原理与性能调优

    来源 | 码哥字节 上帝视角拆解 Tomcat 架构设计,在了解整个组件设计思路之后.我们需要下凡深入了解每个组件的细节实现.从远到近,架构给人以宏观思维,细节展现饱满的美.关注「码哥字节」获取更多硬 ...

  8. 了解架构设计远远不够!一文拆解 Tomcat 高并发原理与性能调优

    来源 | 码哥字节 上帝视角拆解 Tomcat 架构设计,在了解整个组件设计思路之后.我们需要下凡深入了解每个组件的细节实现.从远到近,架构给人以宏观思维,细节展现饱满的美.关注「码哥字节」获取更多硬 ...

  9. jvm第五节-性能调优工具使用

    为什么80%的码农都做不了架构师?>>>    很多开发人员都不是很了解,jdk在安装的时候在bin目录下有很多方便我们调试的工具,有的工具是非常好用的,下面介绍一下jdk自带的调优 ...

  10. JVM原理和性能调优

    JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界 ...

最新文章

  1. Fragment为什么须要无参构造方法
  2. Wireshark实验 - 入门
  3. JVM 史上最最最完整深入解析(12000 字噢)
  4. 工作区 暂存区 版本库之间的关系
  5. 【电信增值业务学习笔记】10基于业务节点的增值业务提供技术
  6. 每日一笑 | 一些关于集合的知识
  7. python发送包含html、图片、附件和链接的邮件
  8. git连接jenkins_开普勒云平台:如何配置gitlab与Jenkins
  9. 关于技术管理者应该如何做好工作安排的一点思考
  10. linux桌面_使用 KDE Plasma 定制 Linux 桌面 | Linux 中国
  11. [转]WCF绑定选择
  12. python实例化次数怎么算_关于python多次实例化
  13. DevExpress 表中数据导出
  14. 在Android中调用KSOAP2库访问webservice服务出现的服务端返回AnyType{}
  15. Eclipse下找不到“新建Web项目”
  16. 深度学习与计算机视觉教程(7) | 神经网络训练技巧 (下)(CV通关指南·完结)
  17. java public interface_Java 接口interface的基础
  18. linux权限英文,Linux常见英文报错中文翻译(菜鸟必知)
  19. 【英语学习】英语语法术语表 English Grammar Terminology
  20. 解散群通知怎么写_家人微信群想解散通知怎么写

热门文章

  1. 360怎样修改wifi服务器地址,360安全路由器IP地址设置的具体操作方法介绍
  2. psm倾向得分匹配法举例_倾向得分匹配(PSM)操作过程与问题反思
  3. MAC 下如何更改brew源地址
  4. 13个医学图像 AI 入门项目- 都跑完你就超神了!
  5. go reflect详解
  6. Java中T和?的区别
  7. 4 Three.js一个案例详解
  8. Groovy - Groovy ambiguous method overload
  9. 无损音频flac转mp3
  10. 怎样搭建电子商务平台网站