再析jvm

news2024/9/28 17:26:17

前言

希望自己每一次学习都有不同的理解

文章目录

    • 前言
    • 1. jvm的组成
        • 取消永久代使用元空间原因
    • 2. 运行时数据区
    • 3. 堆栈区别
    • 队列和栈,队列先进先出,栈先进后出从栈顶弹出
    • 4. GC、内存溢出、垃圾回收
      • 4.1 如何确定引用是否会被回收
        • 4.1.1 Java中的引用类型
        • 4.1.2 如何定位对象
      • 4.2 垃圾回收算法以及收集器
      • 4.3 分代垃圾回收器是怎么工作的
      • 4.4 jvm的类加载器
        • 什么是双亲委派机制
        • 类的加载过程
      • 4.5 jvm调优
        • 流程
        • 参数
        • 如何调整参数

1. jvm的组成

  • Jdk1.6及之前:方法区(永久代), 常量池在方法区
    在这里插入图片描述

  • Jdk1.7:有永久代,但已经逐步“去永久代”,常量池在堆

  • Jdk1.8及之后: 无永久代,元空间, HotSpot JVM
    在这里插入图片描述

  1. 类加载子系统 Class loader 装载class文件到Runtime data area中的method area

  2. 执行引擎 Execution engine执行classes中的指令

  3. 本地方法接口库,与native libraries交互,是其它编程语 言交互的接口

  4. 运行时数据区

首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader) 再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方 法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作 系统去执行,因此需要特定的命令解析器执行引擎(ExecutionEngine),将 字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他 语言的本地库接口(Native Interface)来实现整个程序的功能。

取消永久代使用元空间原因

1、字符串存在永久代中,容易出现性能问题和内存溢出。

2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

元空间的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。

2. 运行时数据区

在这里插入图片描述

  • 程序计数器:线程所执行的字节码的行号 指示器

  • :存储栈帧,栈帧包含局部变量表、操作 数栈、动态链接、方法出口等信息

  • 本地方法栈:与线程栈一样,但是是为调用native方法服务

  • :内存大的一块,是被所有线程共享 的,几乎所有的对象实例都在这里分配内存,1.7之后常量、静态变量也在此区

  • 方法区:存储已被虚拟机加载的类信息,、即时编译后的代码等数据,1.8后改为元空间

Java中的常量池,实际上分为两种形态:静态常量池运行时常量池

  • 静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间
  • 运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池

3. 堆栈区别

  • 物理地址分配

    堆的是不连续的,性能相对慢一些,GC的时候也要考虑到 不连续的分配,所以有各种算法,比如标记-消除,复制,标记-压缩,分代 (即新生代使用复制算法,老年代使用标记—压缩

    栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的

  • 内存

    堆内存远大于栈

  • 内容以及范围

    堆内主要存放数组、对象,关注的是数据的存储,对整个应用是可见的,静态变量是存在于方法区的,但是静态的对象是存在堆

    栈主要存放局部变量、操作数栈、返回结果,关注的是方法的执行,仅对线程可见

队列和栈,队列先进先出,栈先进后出从栈顶弹出

4. GC、内存溢出、垃圾回收

内存溢出也就是jvm发生了Full GC,

永久代(java8之前)内存超出临界值后也会Full Gc,Java8之后取消永久代,增加元空间

原因:长生命周期的对象拥有短生命周期对象的引用,导致短生命周期对象未能被释放导致

4.1 如何确定引用是否会被回收

  1. 可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。 当一个对象到 GCRoots 没有任何引用链相连时,则证明此对象是可以被回收的。

​ GC Root 对象可以是一些静态的对象,Java方法的local变量或参数, native 方法引用的对象,活着的线程,虚拟机栈中引用的对象

  1. 引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用 被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用 的问题

4.1.1 Java中的引用类型

  • 强引用:长期不会被释放
  • 软引用:有用但不必须,在发生内存溢出之前会被回收
  • 弱引用:有用但不必须,在下一次GC时会被回收
  • 虚引用:无法通过虚引用获得对象,虚引用的用途是在 gc 时返回一个通知,用PhantomReference 实现虚引用

4.1.2 如何定位对象

主要通过jvm线程栈上的引用访问jvm堆,现在主流的方式有句柄式和直接指针;

  • 直接指针: 直接指向对象,优势是速度更快,节省了一次指针定位的开销。

  • 句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是 指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的 真实内存地址。

    Java堆中有一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中 包含了对象实例数据对象类型数据各自的具体地址信息,具体构造如下图所 示

    优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是 非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aOOc9qVj-1677148411673)(/Users/zhipengsong/Library/Application Support/typora-user-images/image-20230223162718403.png)]

4.2 垃圾回收算法以及收集器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E9fwn7l6-1677148411673)(/Users/zhipengsong/Library/Application Support/typora-user-images/image-20230223151320058.png)]

  • 标记-清除算法:标记无用对象,然后进行清除回收。

    • 优点:实现简单,不需要对象进行移动,后面算法以此为基础
    • 缺点:效率不高,产生大量不连续的内存碎片,无法清除。
  • 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的 对象复制到另一块上,然后再把已使用的内存空间一次清理掉。

    • 优点:解决了内存碎片的问题,
    • 缺点:内存使用率不 高,只有原来的一半。
    • 收集器
      • Serial收集器: 新生代单线程收集器,标记和清理都是单线程,优点 是简单高效
      • ParNew收集器:多线程版本
      • Parallel Scavenge收集器:新生代并行收集器,追求高吞吐量,高效 利用 CPU。吞吐量= 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高 效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不 高的场景
  • 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清 除掉端边界以外的内存。

    • 优点:解决了内存碎片的问题

    • 缺点:局部对象移动降低效率

    • 收集器

      • Serial Old收集器: 老年代单线程收集器,Serial收集器的老年代版本;
      • Parallel Old收集器 : 老年代并行收集器,吞吐量优先, Parallel Scavenge收集器的老年代版本;

      在这里插入图片描述

  • 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代和永久代(元数据区)

    • 新生代基本采用复制算法

    • 老年代采用标记整理算法

      • CMS收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最 短GC回收停顿时间,但是会出现大量的内存碎片,当内存不够使用的时候会采用 Serial Old 回收器进行垃圾清除
      • G1收集器 (标记-整理算法): Java堆并行收集器,基于标记-整理算法,但是G1回收的范围是整个Java堆(包括新生代,老年代)

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8FzMteEI-1677148411673)(/Users/zhipengsong/Library/Application Support/typora-user-images/image-20230222141923519.png)]

**在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。 **

4.3 分代垃圾回收器是怎么工作的

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是2/3。

新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

  1. 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
  2. 清空 Eden 和 From Survivor 分区;
  3. From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年 龄到达 15(默认配置是15)时,升级为老生代。大对象也会直接进入老生代

老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的 执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程

对象分配:

java对象优先分配在eden区,大对象(需要连续的内存空间的对象,不经过eden和survivor)和长期存活的对象分配至老年代

4.4 jvm的类加载器

启动类加载器:负责加载JRE的核心类库,如jre目标下的rt.jar,charsets.jar等

扩展类加载器:负责加载JRE扩展目录ext中JAR类包

系统类加载器:负责加载ClassPath路径下的类包

用户自定义加载器:负责加载用户自定义路径下的类包 (继承ClassLoader,然后覆盖findClass()方法)

什么是双亲委派机制

在这里插入图片描述

要加载一个类MyClass.class,从低层级到高层级一级一级委派,先由应用层加载器委派给扩展类加载器,再由扩展类委派给启动类加载器;启动类加载器载入失败,再由扩展类加载器载入,扩展类加载器载入失败,最后由应用类加载器载入,如果应用类加载器也找不到那就报ClassNotFound异常了

双亲委派机制的优点:

1.保证安全性,层级关系代表优先级,也就是所有类的加载,优先给启动类加载器,这样就保证了核心类库类。

2.避免重复,如果父类加载器加载过了,子类加载器就没有必要再去加载了

类的加载过程

  1. 加载
    1. 通过过一个类的全限定名获取该类的二进制流
    2. 将该二进制流中的静态存储结构转化为方法去运行时数据结构
    3. 在内存中生成该类的 Class 对象,作为该类的数据访问入口
  2. 验证
    1. 文件格式验证:验证字节流是否符合 Class 文件的规范,常量池中的常量是否有不被支持的类型
    2. 元数据验证: 对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
    3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
    4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。
  3. 准备 : 为类的变量以及静态变量分配内存并将其初始化为默认值
  4. 解析:要完成符号引用到直接引用的转换动作
  5. 初始化:到了初始化阶段,才真正开始执行类中定义的 Java 程序代码

4.5 jvm调优

在这里插入图片描述

性能调优指标:

  • 吞吐量:重要指标之一,是指不考虑垃圾收集引起的停顿时间或内存消耗,垃圾收集器能支撑应用达到的最高性能指标。
  • 延迟:其度量标准是缩短由于垃圾啊收集引起的停顿时间或者完全消除因垃圾收集所引起的停顿,避免应用运行时发生抖动。
  • 内存占用:垃圾收集器流畅运行所需要 的内存数量。

这三个属性中,其中一个任何一个属性性能的提高,几乎都是以另外一个或者两个属性性能的损失作代价,不可兼得,具体某一个属性或者两个属性的性能对应用来说比较重要,要基于应用的业务需求来确定。

流程

  1. 工具:
    1. 于 JDK 的 bin 目录下的工具:jconsole、jvisualvm、jmap
    2. mat工具,可以分析到具体某个线程,某个对象占用了空间
  2. 检查范围
    1. Dump线程详细信息:查看线程内部运行情况,内存热点分析
    2. 死锁检查
    3. 线程监控

调优的目的是什么:

  • GC的时间足够的小
  • GC的次数足够的少
  • 减少老年代的对象数量
  • 发生Full GC的周期足够的长

但是很明显,第一条和第二条相悖,因为GC时间小必然需要一个较小的堆,而次数少则必然需要一个较大的堆

参数

为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值

  • -Xms2g:初始化推大小为 2g;
  • -Xmx2g:堆最大内存为 2g;
  • -XX:NewSize :新生代空间大小初始值
  • -XX:MaxNewSize :新生代空间大小最大值
  • -Xmn :新生代空间大小,此处的大小是(eden+2 survivor space)

如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。可以通过监控堆状态老年代内存占有情况来决定比例

  • -XX:NewRatio=2:设置年轻的和老年代的内存比例为 1:2;默认为1:2
  • -XX:SurvivorRatio=8:设置新生代Eden 和 Survivor 比例为 8:2;
  • –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
  • -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
  • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组 合;
  • -XX:+PrintGC:开启打印 gc 信息;
  • -XX:+PrintGCDetails:打印 gc 详细信息。
  • XX:MetaspaceSize=2M : 元空间初始大小,如果没有指定这个参数,元空间会在运行时根据需要动态调整,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:+UseParallelGC :选择垃圾收集器为并行收集器。 此配置仅对年轻代有效。即上述配置下,年轻代 使用并发收集,而年老代仍旧使用串行收集。 

-XX:ParallelGCThreads=20 :配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值 最好配置与处理器数目相等。 

-XX:+UseParallelOldGC :配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。 

-XX:MaxGCPauseMillis=100 : 设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动 调整年轻代大小,以满足此值。 

-XX:+UseAdaptiveSizePolicy :设置此选项后,并行收集器会自动选择年轻代区大小和相应的 Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直 打开。

如何调整参数

系统完成之后不对jvm进行参数调整,进行压测,查看GC日志,GC日志指令: -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:

fullGC后查看GC数据
在这里插入图片描述

之后可以根据一下修改配置,修改之后再进行压测

在这里插入图片描述

User user = new User()做了什么操作,申请了哪些内存

  1. new User(); 创建一个User对象,内存分配在堆上

  2. User user; 创建一个引用,内存分配在栈上

  3. = 将User对象地址赋值给引用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366545.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenStack手动分布式部署Glance【Queens版】

目录 Glance简介 1、登录数据库配置(在controller执行) 1.1登录数据库 1.2数据库里创建glance 1.3授权对glance数据库的正确访问 1.4退出数据库 1.5创建glance用户密码为000000 1.6增加admin角色 1.7创建glance服务 1.8创建镜像服务API端点 2、安装gla…

LeetCodeHOT100热题02

写在前面 主要是题目太多,所以和前面的分开来记录。有很多思路的图都来源于力扣的题解,如侵权会及时删除。不过代码都是个人实现的,所以有一些值得记录的理解。之前的算法系列参看: 剑指offer算法题01剑指offer算法题02 七、动…

SQL零基础入门学习(八)

SQL零基础入门学习(七) SQL 连接(JOIN) SQL join 用于把来自两个或多个表的行结合起来。 下图展示了 LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相关的 7 种用法。 SQL JOIN SQL JOIN 子句用于把来自两个或多个表的行结合起来,基…

TensorBoard自定义修改单条及多条曲线颜色

在深度学习可视化训练过程中,曲线颜色是随机的,想要将好看的曲线颜色图放到论文中,就得自定义曲线颜色,具体方法见下文。 目录一、下载svg文件二、修改svg文件三、修改后曲线颜色对比四、总结一、下载svg文件 在TensorBoard界面中…

webman 连接 oracle

composer require topthink/think-oraclev2.1 配置文件 thinkorm.php return [ default > oracle, connections > [ oracle > [ // 数据库类型 type > oracle, // 服务器地址 hostname > 192…

多语言解决方案

文章目录背景整体方案多语言管理端客户端流水线其他背景 多语言是一个比较麻烦的事情,特别是当 App 比较大的时候,还会涉及到多个部门的开发以及翻译人员,这中间可能会存在比较大的沟通成本,而且还可能会阻塞开发的进度。以下是我…

【JavaEE初阶】第二节.多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、synchronized的优化操作 1.1 锁膨胀/锁升级 1.2 锁消除 1.3 锁粗化二、JUC 2.1 Callable接口 2.2 ReentrantLock类&…

Python获取中国大学MOOC某课程评论及其参与人数

文章目录前言一、需求二、分析三、运行结果前言 本系列文章来源于真实的需求本系列文章你来提我来做本系列文章仅供学习参考 一、需求 1、课程参加人数 2、课程学员名称及其评论 二、分析 首先查看网页源代码是否有需要的数据 课程参加人数 课程学员名称及其评论 F12 打开浏…

Linux中断处理

目录 一、什么是中断 二、中断处理原理 三、中断接口 3.1 中断申请 3.2 中断释放 3.3 中断处理函数原型 四、按键驱动 一、什么是中断 一种硬件上的通知机制,用来通知CPU发生了某种需要立即处理的事件 分为: 1. 内部中断 CPU执行程序的过程中&am…

力扣sql简单篇练习(二十)

力扣sql简单篇练习(二十) 1 广告效果 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 SELECT ad_id,IFNULL(ROUND(sum(IF(actionClicked,action,0))/sum(IF(actionIgnored,0,1))*100,2),0.00) ctr FROM Ads GROUP BY ad_id ORDER BY ctr desc,ad_id …

消息队列MQ介绍

消息队列技术是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上,队列存储消息直到它们被应用程序读走。通过消息队列,应用程序可独立地执行--它们不需要知道彼此的位置、或在继续执行前不需要等待接收程序接收此消息。 消息中间件概述 消息队列技术是…

Wi-Fi 7技术揭秘

引言 2022年4月7日,紫光股份旗下新华三集团全球首发企业级智原生Wi-Fi 7 AP新品 WA7638和WA7338。仅在同年的6月15日,在东京举行的第29届日本网络通信展览会(Interop Tokyo 2022,简称Interop展)中,WA7638就…

Java - 数据结构,栈

一、栈 1.1、什么是栈 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈 顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压…

关于《How to Learn to Code Get a Developer Job in 2023》的经验学习

1. Who is This Book For ? for Anyone who is considering a career in software development. 2. Can Anyone Learn to Code? Any sufficiently motivated person can learn to code. 3.Executive Summary Learning to code is hard.Getting a job as a software devel…

01-基于SOA架构someip 开发-Linux开发环境搭建

前言:SOME/IP 是一个汽车的中间件解决方案,可用于控制消息。从一开始,它的设计就是为了完美地适应不同尺寸和不同操作系统的设备。这包括小型设备,如相机、AUTOSAR设备,以及头部单元或远程信息处理设备。同时还确保了S…

华为OD机试题,用 Java 解【VLAN 资源池】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

脑洞|ChatGPT加持下,ChatOps将如何革新团队协作与运维管理?

要说近期科技圈 “顶流”,非 ChatGPT 莫属。 比起目前常见的语音助手与聊天 bot,这位机器人显得更有 “人味儿”,不仅能模拟人类的语气,跟你聊得有来有回,还能写剧本、编音乐、写代码。 说到聊天工具,就让…

低代码开发可以解决哪些问题?

低代码开发可以解决哪些问题?如果用4句话去归纳,低代码开发可以解决以下问题—— 为企业提供更高的灵活性,用户可以突破代码的限制自主开发业务应用;通过减少对专业软件开发人员的依赖,公司可以快速响应市场上的新业务…

完全背包—动态规划

一、背包问题概述 如图,完全背包与01背包的区别只有一点:01背包中每个物品只能取一个而完全背包中每个物品可以取无数个。解决完全背包问题必须首先弄明白01背包,不清楚的可以看我的这篇文章01背包—动态规划。 二、例题 重量价值物品0115物…

Jenkins+docker发布Springbot服务

1.开发Springbot应用 新建多个环境的配置文件 bootstrap.yaml 通过变量获取不同环境active bootstrap-dev.yml bootstrap-pre.yaml 预发布及生产环境配置文件走nacos 二.配置docker 新增Dockerfile文件 Dockerfile内容 # Docker image for springboot file run # VERSION…