当前位置: 首页 > article >正文

【JVM】JVM自学笔记(类加载子系统、运行时数据区、执行引擎)

JVM自学笔记

  • 引言
  • 总结
  • JVM跨平台
  • JVM组成部分
    • 类加载子系统
    • 运行时数据区
      • 程序计数器
      • 虚拟机栈
      • 本地方法栈
  • 执行引擎
    • 垃圾回收

引言

主要内容为学习b站视频后的笔记+部分个人总结。原视频链接为:【【JVM极简教程】2小时快速学会JVM,史上用时最短,效率最高!】

总结

在这里插入图片描述
类加载子系统

  • 类加载子系统是将字节码从磁盘加载到内存中,同时完成验证字节码文件是否正确、初始化变量、为对象的引用地址放入实际的物理地址操作。
  • 类加载子系统主要分为两种,一个是引导类加载器,一个是自定义类加载器。
  • 代码中使用类加载器是从子类向上找其父类,优先由父类进行加载,如果父类找不到,在层级向下进行加载。
  • Tomcat自定义加载器为了保证不同应用中相同名的类能重复加载,实现应用之间的类隔离。

方法区

  • 方法区存放着Java所用的方法、常量、类信息。

Java方法栈

  • Java方法栈中有多个栈帧,当一个方法使用了其他方法后,根据调用层级生成多个栈帧。栈帧中有局部变量表和操作数栈,两个根据字节码中的操作数填写变量表。一个方法栈对应一个线程。

本地方法栈

  • 本地方法栈中为Java定义中所使用的方法,同样是一个方法栈对应一个线程。

程序计数器

  • 程序计数器则是记录下一个指令的地址,是JVM中唯一不会发生内存溢出情况的。

  • 堆中存储着对象和数组。堆中分为新生代(Eden区、S0、S1区)和老年代。老年代比新生代大,两者为2:1。新对象放入新生代Eden区而后经过垃圾回收(GC)后留存的有用的对象放入S0,再GC后放入S1,重复在S0和S1跳跃,直到第16次还可留存,就放入老年代。当遇到大对象的时候有两种存储方式:1.大对象无法放入S0,先放Eden,GC后放入老年代 2. 过大对象无法放入Eden,直接放入老年代。

垃圾回收器

  • 垃圾回收清理没有使用的对象,释放内存空间。
  • 找到垃圾对象的方式:引用计数法,可达性分析法。常用的为后者,通过分析GC Root(栈中正在运行的方法中方法参数、局部对象的引用;方法区中保存的属性所使用的对象引用等)能否通过各层到达对象,来判断对象是否为垃圾对象。
  • 清除垃圾的方法:标记-清除法、复制法、标记-整理法。
  • 垃圾清除方法采用分代收集算法,根据不同的代(老年代/新生代)进行安排不同算法。不同垃圾收集器采用不同的算法用于不同的代中,根据实际情况安排使用。

JVM跨平台

Java文件编译为字节码,字节码解释成机器语言。
跨平台本质是不同系统有不同JVM,不同JVM解释后得到不同的结果。

为什么不直接将Java代码解释为机器语言?(类似Python这样的解释语言)
原因:效率。Java直接编译字节码后再解释,运行效率更高(相当于解释前提前处理)。
其他语言需要跨平台则将语言翻译为字节码后利用不同平台的JVM即可完成。

JVM组成部分

类加载子系统

在这里插入图片描述
作用:将class字节码文件从磁盘读取到内存,供给CPU调用。类加载后放入方法区,形成一个class对象。

  • 准备阶段:将常量赋初值,例如static int a = 0
  • 解析阶段:将class文件中所使用的其他类的符号引用(类名称)转换为直接引用(在方法区中,找到对应的地址)。

初始化对象给static属性赋值,如果代码里面为static int a = 10,则给a赋值10。

在这里插入图片描述
类加载器默认三个是BootStrapClassLoader、ExtClassLoader(扩展类加载器)、AppClassLoader(应用类加载器)。三个类加载器的区别在于加载的目录不一样,分别为jre|Lib、jre|Lib|ext、当前应用的classpath所指定的加载器
WebAppClassLoader是Tomcat中继承ClassLoader的加载器,属于严格意义上的自定义类加载器。

双亲委派

源代码中:
在这里插入图片描述

概念图:
在这里插入图片描述

根据代码可以看到,在逐层向上的调用加载器。
在代码中使用类加载器loadClass方法中会判断,当该类加载器有父类时先调用其父类加载器加载,若父类加载器父类,则使用BootStrapClass进行加载。如果上级父类在其加载的目录中找不到对应的类,则层层返回原子类进行加载。

好处:避免类的重复加载;防止核心API被篡改

之所以能够防止核心API被篡改,原因在于类加载器会使用的目录是核心处,哪怕代码中导入了包名类名组成的路径和核心类一样的非核心类,类加载器都会使用自身目录下的路径一样的类。

在这里插入图片描述
总结:为了实现应用之间的类隔离,相同名字的类可以做到不同应用中重复加载。

运行时数据区

在这里插入图片描述
蓝色区域内容为多个进程共享。绿色区域:每个线程都有自己的Java方法栈、本地方法栈、程序计数器

程序计数器

程序计数器(PC Register)记录程序下一条指令的地址,是物理寄存器的抽象实现,当我们解释器工作时就是使用它进行记录下一个字节码指令,是程序控制流的指示器(循环、if else、异常处理、线程恢复都依赖它完成),是JVM中唯一一个不会发生内存溢出的(一个线程有一个程序记录器,要存放的数字长度多大,程序计数器就有多大)

虚拟机栈

在这里插入图片描述

  • 虚拟机栈是线程私有的。
  • 每个方法生成一个栈帧,当方法一调用了方法二的时候,为方法二生成一个栈帧。当逐层的顶层(图中方法四)执行完后,当前栈帧出栈。当所有栈帧出栈后,该栈自动销毁,不需要进行垃圾回收。
  • 虚拟机栈存在OutOfMemoryError(内存溢出,原因:线程创建时没有足够的内存去创建虚拟机栈)、以及StackOverflowError(栈溢出,原因:方法调用层次太多,生成了太多栈帧),可以通过-Xss来设置虚拟机栈的大小。

在这里插入图片描述
局部变量表存放方法所用的变量,表分为一个一个的slot存放量
操作数栈是在执行字节码指令的时候计算的。

在这里插入图片描述
以上图片中,先读取操作数10入栈操作数栈,而后出栈将该数存放到局部变量表1。

本地方法栈

本地方法:native method,在Java中定义的方法,但由其他语言(C/C++)实现。

在这里插入图片描述

  • 数组和对象需要存放在堆区域,执行字节码指令时,会将创建的对象放入堆中。

  • 栈帧中存放的是对象的引用地址,当方法执行结束,栈帧消失后,其所使用的对象并不会消失,而是等待JVM后台执行垃圾回收(GC)后才会消失。
    -Xms:ms(memory start),指定堆的初始化内存大小,等价于-XX:InitialHeapSize
    -Xmx:mx(memory max),指定堆的最大内存大小,等价于-XX:MaxHeapSize
    一般会把-Xms和-Xmx设置为一样,这样JM就不需要在GC后去修改堆的内存大小了,提高了效率
    默认情况下,初始化内存大小=物理内存大小/64,最大内存大小=物理内存大小/4

  • 新生代是新生成的对象,老年代是经过多次垃圾回收后还存活的对象,比例为1:2,即为新生代1/3。调整参数-XX:NewRatio配置两者占比。

  • Eden:伊甸园区,新对象都会先放到Eden区(除非对象的大小都超过了Eden区,那么就只能直接进老年代)。

  • S0、S1:Survivor0、Survivor1区,也可以叫做from区、to区,用来存放MinorGC(YGC)后存在的对象。

Eden区:S0区:S1区,比例关系为(8:1:1),可通过-XX:SurvivorRatio来调整。

在这里插入图片描述
首先新对象放入Eden区域,而后进行垃圾回收一次后放入S0或S1,进行过的对象会进行计数,之前在S0的放入S1,这样反复在S0和S1跳跃,当第16次回收也没被回收掉则将对象放入老年代中。
有两个特殊情况:

  • 放入大对象,Eden放的下,S0和S1大小是一样的,都放不下,垃圾回收执行一次后发现存活,直接放入老年代、
  • 放入超大对象,Eden放不下,直接放入老年代。

在这里插入图片描述
Young GC/Minor Gc:负责对新生代进行垃圾回收
Old GC/Major GC:负责对老年代进行垃圾回收,目前只有CMS垃圾收集器会单独对老年代进行垃圾收集,其他垃圾收集器基本都是整堆回收的时候对老年代进行垃圾收集
Full GC:整堆回收,也会堆方法区进行垃圾收集

执行引擎

垃圾回收

为什么回收
垃圾是指在JVM中没有任何引用指向它的对象,如果不清理这些垃圾对象,那么它们就一直占用着内存,而不能给其他对象使用,最终垃圾对象越来越多,就会出现OOM(outofMemory)了。

找到垃圾对象的方式
1、引用计数法
每个对象都保存一个引用计数器属性(需要额外的空间来存储引用计数),用户记录对象被引用的次数。
优点:实现简单,计数器为0则表示是垃圾对象缺点
严重问题:无法处理循环引用的问题。当AB两个对象互相引用且两者都没有其他对象引用它们,即使两者是垃圾对象也无法被删除。

2、可达性分析法
在这里插入图片描述
以GC Roots作为起始点,然后一层一层找到所引用的对象,被找到的对象就是存活对象,那么其他不可达的对象就是垃圾对象。
GC Roots是一组引用,包括:

  • 线程中虚拟机栈、本地方法栈中正在执行的方法中方法参数、局部变量所对应的对象引用
  • 方法区中保存的类信息中静态属性、常量属性所对应的对象引用
  • 等等

如何清除垃圾对象

1、标记-清除法
针对某块内存空间(新生代、老年代),当内存空间不够用的时候就会STW,暂停用户线程,执行算法进行垃圾回收。

算法两个阶段,第一标记阶段使用可达性分析法,可达的对象在对象头上进行标记,第二阶段则在堆内存空间中进行线性遍历,未发现对象头标记则进行清理。

缺点:造成过多碎片空间;效率不高

概念图:
结果后产生很多空闲空间(灰色),但分不不均匀,所以缺点为造成过多碎片空间
在这里插入图片描述

2、复制算法

将内存空间分为两块,直接遍历有对象的一块A,当发现可达对象直接放入B,遍历完后清除A所有对象,后续进行同样的垃圾回收流程。

优点:避免内存碎片;效率高
缺点:当A中可达对象多,复制过去的消耗时间也多;需要内存大,始终有一半空闲内存;复制后内存地址变化导致需要时间去修改虚拟机栈中的引用地址。

对比标记清除法:

  • 标记清除法遍历了两次对象,第一次标记,第二次清除,复制法只有一次。
  • 复制法只有一个线程,标记清除则有两个线程去执行。

在这里插入图片描述

3、标记整理算法

第一阶段进行标记可达对象,第二阶段将可达对象移动到内存另一端,第三段清理空间。
在这里插入图片描述

缺点:效率比另外两个更低;需要修改栈中使用的对象的引用地址。
[图片]

实际使用的垃圾清除是哪种方式?
默认所有的垃圾收集器采用分代收集算法,因为不同对象有不同存活时间,所以有不同的垃圾回收算法。

将堆分为新生代和老年代,新生代存活时间短,利用复制算法更快清除,适用垃圾对象多的情况;老年代则标记清除法或者标记整理法,CMS垃圾收集器采用的就是标记-清除算法,Serial Old垃圾收集器采用的就是标记-整理算法。

在这里插入图片描述
在这里插入图片描述
比如一次复制算法,有多个线程进行将保留对象复制到其他空间,将用户工作线程暂停后运行多个GC线程。

CMS GC (老年代使用,标记清除算法)

CMS整个垃圾收集过程更长了,但是STW的时间变短了,而且在垃圾收集过程中大部时间用户线程也还在执行,所以用户体验更好了,但是吞吐量更低了(单位时间内执行的用户线程更少了)
在这里插入图片描述
初始标记:通过GC root找到直接可达对象(一层)
并发标记:GC线程基于刚找到的可达对象,找到所有可达对象,涉及三色标记
重新标记:解决上一步可能发生的误差,进行重新标记
并发重置:重置之前的标记,方便下一次进行垃圾回收

如果在并发标记、并发清理过程中,由于用户线程同时在执行,如果有新对象要进入老年代,但是空间又不够(原因:垃圾回收器工作速度比用户创造垃圾的速度快),那么就会导致"concurrent mode failure”,此时就会利用Serial Old(暂停所有用户线程)来做一次垃圾收集,就会做一次全局的STW。

在并发清理过程中,可能产生新的垃圾(原因:用户线程还在执行),这些就是“浮动垃圾”,只能等到下一次GC时来清理
由于采用的时标记-清除,所以会产生内存碎片,可以通过参数-XX:+UseCMSCompactAtFulCollection可以让JVM在执行完标记-清除后再做一次整理,也可以通过-XX:CMSFullGCsBeforeCompaction来指定多少次GC后来做整理,默认是0,表示每次GC后都整理。

G1(Garbage-First)
在这里插入图片描述
将新生代、老年代在物理上分为一个个方块region,一个堆2048个region,每个region大小为堆内存除以2048。

还是分了Eden区、S0区、S1区、老年代,只不过空间可以是不连续的了Humongous区是专门用来存放大对象的(如果一个对象大小超过了一个region的50%,那么就是大对象)

在这里插入图片描述
筛选回收阶段:通过算法计算垃圾对象多的区域,优先清除这些区域。可以设置回收所花的时间,可能会在时间内没有清理完成,但对用户体验有帮助。可以通过-XX:MaxGCPauseMiis来指定GC的STW停顿的时间,所以可能并不会回收掉所有垃圾对象,默认200ms
采用的复制算法,不会产生碎片(会把某个region里的可达对象复制到另外空闲region区域,比如相邻的),回收的是整个region


http://www.kler.cn/a/464809.html

相关文章:

  • Python自学 - 函数式编程初步(lambda、map、filter)
  • 以太网ICMP协议(ping指令)——FPGA学习笔记25
  • 【Go学习】-01-2-常见类型及关键字
  • 从零开始RTSP协议的实时流媒体拉流(pull)的设计与实现(一)
  • 非docker方式部署openwebui过程记录
  • 我的博客年度之旅:感恩、成长与展望
  • Python世界:高频小技巧总结
  • 低代码开发:开启企业数智化转型“快捷键”
  • Python 图像处理:生成美丽的书籍封面
  • torch.nn.functional的用法
  • 小程序学习05——uniapp路由和菜单配置
  • 判断旗帜是否符合ISO新标准
  • 基于springcloud分布式的网上商城系统设计与实现【源码+文档+部署讲解】
  • Spark-Streaming有状态计算
  • 十个Scala的小知识
  • 通过blob请求后端导出文件
  • idea项目导入gitee 码云
  • aws(学习笔记第二十一课) 开发lambda应用程序
  • 【3D开发SDK】HOOPS助力NAPA:打造高效三维船舶设计平台
  • 【赵渝强老师】MongoDB的Journal日志
  • Elasticsearch: 高级搜索
  • 华为OD E卷(100分)42-矩形相交面积
  • webserver的http实现
  • MATLAB中whitespacePattern函数用法
  • maya 删除 Ctrl + Delete vs Delete
  • Python实现Excel行列转换