深入理解JVM:内存结构、垃圾收集与性能调优

目录

JDK、JRE、JVM关系?

启动程序如何查看加载了哪些类,以及加载顺序?

class字节码文件10个主要组成部分?

JVM结构

画一下JVM内存结构图

程序计数器

Java虚拟机栈

本地方法栈

Java堆

方法区

运行时常量池?

什么时候抛出StackOverflowError?

例如:

Java7和Java8在内存模型上有什么区别?

程序员最关注的两个内存区域?

直接内存是什么

除了哪个区域外,虚拟机内存其他运行时区域都会发生OutOfMemoryError?

什么情况下会出现堆内存溢出?

空间什么情况下会抛出OutOfMemoryError?

如何设置直接内存容量?

Java堆内存组成?

Edem:from:to默认比例是?

垃圾标记阶段?

引用计数法?

根搜索算法?

JVM中三种常见的垃圾收集算法?

标记-清除算法(Mark_Sweep)

复制算法(Copying)

标记-压缩算法(Mark-Compact)

分代收集算法?

垃圾收集器

Stop The World

Serial收集器

PartNew收集器

Parallel Scavenge

Parallel Old收集器

CMS 收集器

CMS垃圾回收的步骤?

CMS收集器优点?缺点?

G1收集器?

G1收集器是如何改进收集方式的?


JDK、JRE、JVM关系?

Jdk (Java Development Kit):java语言的软件开发包。包括Java运行时环境Jre。

Jre (Java Runtime Environment):Java运行时环境,包括Jvm。

Jvm (Java Virtual Machine) :

  • 一种用于计算机设备的规范。
  • Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虛拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

Jdk包括Jre,Jre包括Jvm。

启动程序如何查看加载了哪些类,以及加载顺序?

Java -XX:+TraceClassLoading 具体类
Java -verbose 具体类

class字节码文件10个主要组成部分?

  • MagicNumber
  • Version
  • Constant_pool
  • Access_flag
  • This_class
  • Super_class
  • Interfaces
  • Fields
  • Methods
  • Attributes

JVM结构

画一下JVM内存结构图

程序计数器

属于线程私有内存。占用一块非常小的空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的指令的字节码,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器来完成。

Java虚拟机栈

属于线程私有内存。它的生命周期与线程相同,虚拟机栈描述的是Java方法执行内存模型;每个方法被执行的时候都会同时创建一个栈桢用于存储局部变量表、操作栈、动态链接、方法出口信息等。每一个方法被调用直至执行完成的过程,就对应着一个栈帧再虚拟机中从入栈到出栈的过程。

本地方法栈

本地方法栈与虚拟机栈所发挥的作用是非常相似的,只不过虚拟机栈对虚拟机执行Java方法服务,而本地栈是为虚拟机使用到Native方法服务。

Java堆

是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Tps:但随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标亮替换优化技术将会导师一些微妙的变化发生,所有的对象都分配在堆上就不那么绝对了

方法区

是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

运行时常量池?

是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息,还有一项是常量池(Constant PoolTable)用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放道方法区的运行时常量池中。

什么时候抛出StackOverflowError?

如果线程请求的栈深度大于虚拟机所允许的深度,则抛出StackOverflowError。

例如:

public class RecursiveOverflow {  public static void main(String[] args) {  recursiveMethod();  }  public static void recursiveMethod() {  recursiveMethod(); // 无限递归  }  
}
  1. 递归调用没有合适的终止条件:当一个方法直接或间接地递归调用自己,并且没有适当的终止条件时,调用栈会不断增长,直到耗尽栈空间。

  2. 方法调用层次过深:即使不是递归调用,但如果方法调用的层次太深(例如,A 方法调用 B 方法,B 方法调用 C 方法,以此类推),也可能导致栈溢出。
  3. 大量的本地变量:虽然这种情况不太常见,但如果在方法中有大量的本地变量(包括对象引用),也可能导致栈帧过大,从而在短时间内耗尽栈空间。
  4. 线程栈大小设置不当:JVM 启动时可以通过 -Xss 参数来设置每个线程的栈大小。如果设置得过小,可能会导致在正常的程序执行过程中就发生栈溢出。

Java7和Java8在内存模型上有什么区别?

  1. Java内存模型(JMM)的改进
    • Java 8引入了新的JMM特性,这些特性主要为了支持更高效的并发编程和多线程应用。JMM定义了一组规则,用于指导程序员编写正确的多线程代码,以避免出现数据竞争和内存可见性问题。
    • Java 8的JMM更加强调了原子性、可见性和有序性。原子性通过volatile关键字和java.util.concurrent.atomic包中的类来实现,确保对变量的操作是不可中断的。可见性确保了一个线程对共享变量的修改对其他线程是可见的。有序性则允许在不影响单线程程序执行结果的情况下对指令进行重排序。
  2. JVM内部变化
    • 方法区变化:Java 8中,方法区(Method Area)的实现由永久代(PermGen)变为了元空间(Metaspace)。这是为了融合HotSpot JVM与JRockit VM而做出的努力,因为JRockit并没有永久代的概念。元空间使用本地内存,而不是虚拟机内存,从而避免了永久代常见的内存溢出问题。
    • 栈和堆的变化:Java 7和Java 8在栈和堆的基本结构上并没有显著变化。Java虚拟机的内存模型中仍然包括Java虚拟机栈、本地方法栈、堆和方法区。Java虚拟机栈和本地方法栈是线程私有的,而堆和方法区是线程共享的。
  3. 内存模型示例代码
    • 尽管Java 7和Java 8在内存模型上有所不同,但使用volatile关键字的示例代码在两者中的表现是相似的。volatile关键字在Java 8中仍然用于确保变量的可见性和禁止指令重排序。

程序员最关注的两个内存区域?

堆(Heap)和栈(Stack),一般大家会把Java内存分为堆内存和栈内存,这是一种比较粗糙的划分方式但实际上Java内存区域是很复杂的。

直接内存是什么

直接内存是Java中用于提高性能的一种重要内存管理方式,特别是在处理大文件读写和NIO操作时。然而,由于它不受JVM的GC管理,使用时需要特别注意内存分配和回收的问题。

  1. 定义
    • 直接内存是指Java堆外内存,即不属于Java虚拟机(JVM)运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
    • 它属于操作系统内存,不由JVM管理,而是直接由Java应用程序通过直接方式从操作系统中申请。
  2. 特点
    • 分配:直接内存的分配不会受到Java堆大小的限制,但会受到本机总内存的大小及处理器寻址空间的限制。
    • 性能:由于直接内存位于堆外,读写操作可以直接在内存和磁盘之间进行,避免了Java堆和native堆中来回复制数据,从而在某些场景下可以显著提高性能。
    • 回收:直接内存的分配与释放是通过一个Unsafe类型对象进行的(释放通过调用freeMemory),而不是通过Java的垃圾回收(GC)机制。
  3. 用途
    • 常用于NIO(New I/O)操作中,如ByteBuffer的分配。使用直接缓冲区(DirectBuffer)时,操作系统划分出的直接缓存区可以被Java代码直接访问,减少了内核态到用户态的相互拷贝,从而提高了文件读写操作的效率。
    • 学习直接内存的目的是因为Java 8中的元空间的落地方案就是直接内存实现的。
  4. 配置
    • 直接内存大小可以通过MaxDirectMemorySize参数进行设置。如果不指定,则默认与堆的最大值(-Xmx参数值)一致。
  5. 注意事项
    • 由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。
    • 如果直接内存使用不当,也可能导致OutOfMemoryError异常。

除了哪个区域外,虚拟机内存其他运行时区域都会发生OutOfMemoryError?

程序计数器

什么情况下会出现堆内存溢出?

  1. 对象过多或过大
    • 应用程序创建了过多的对象,并且这些对象在堆内存中占用了大量的空间。
    • 应用程序创建了非常大的对象,超出了堆内存能够容纳的大小。
  2. 内存泄漏
    • 内存泄漏是指应用程序不再需要某些对象,但由于程序中的错误,这些对象没有被垃圾回收器(Garbage Collector, GC)回收,导致它们仍然占用着内存。随着时间的推移,泄漏的对象会越来越多,最终耗尽堆内存。
  3. 静态集合类
    • 静态集合类(如静态HashMap、静态ArrayList等)的生命周期与应用程序的生命周期相同。如果静态集合类中的对象不再需要,但由于它们是静态的,所以垃圾回收器不会回收它们,从而导致内存泄漏。
  4. 缓存
    • 缓存是为了提高性能而存储的常用数据。但是,如果缓存中的数据量过大,或者缓存的清理策略不当,可能会导致堆内存溢出。
  5. 第三方库
    • 某些第三方库可能存在内存管理问题,导致内存泄漏或大量占用内存。
  6. 堆内存设置过小
    • 如果JVM启动参数中设置的堆内存大小(-Xmx)过小,无法满足应用程序的内存需求,也可能导致堆内存溢出。
  7. 无限递归或大量循环
    • 如果程序中存在无限递归或大量循环,每次递归或循环都创建新的对象,那么这些对象会不断占用堆内存,最终导致溢出。
  8. 大对象数组
    • 如果创建了一个大对象数组,并且数组中的每个对象都很大,那么整个数组会占用大量的堆内存。
  9. 死锁
    • 死锁可能会导致线程阻塞,无法继续执行,从而无法释放已经占用的内存资源,间接导致堆内存溢出。
  10. 垃圾回收器配置不当
    • 如果垃圾回收器的配置不当,可能会导致内存回收不及时或回收效率低下,最终导致堆内存溢出。

为了解决堆内存溢出问题,可以采取以下措施:

  • 分析堆转储(Heap Dump)文件,找出哪些对象占用了大量内存,并确定它们是否应该被回收。
  • 优化代码,减少不必要的对象创建和内存占用。
  • 修复内存泄漏问题,确保不再需要的对象能够被垃圾回收器正确回收。
  • 调整JVM启动参数,增加堆内存大小或优化垃圾回收器配置。
  • 使用内存分析工具(如VisualVM、MAT等)来监控和分析应用程序的内存使用情况。

空间什么情况下会抛出OutOfMemoryError?

如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError。

如何设置直接内存容量?

通过 -XX:MaxDirectMemorySize指定,如果不指定,则默认与]ava堆的最大值一样。

Java堆内存组成?

堆大小=新生代+老年代。如果是]ava8则没有PermanentGeneration。
其中新生代(Young)被分为Eden和S0(from)和S1(to)。

Edem:from:to默认比例是?

Edem :from :to=8 :1 :1
此比例可以通过 -XX:SurvivorRatio 来设定

垃圾标记阶段?

在GC执行垃圾回收之前,为了区分对象存活与否,当对象被标记为死亡时,GC才回执行垃圾回收,这个过程就是垃圾标记阶段。

引用计数法?

比如对象a,只要任何一个对象引用了a,则a的引用计数器就加1,当引用失效时,引用计数器就减1,当计数器为0时,就可以对其回收。
但是无法解决循环引用的问题。

根搜索算法?

跟搜索算法是以跟为起始点,按照从上到下的方式搜索被根对象集合所连接的目标对象是否可达(使用根搜索算法后,内存中的存活对象都会被根对象集合直接或间接连接着),如果目标对象不可达,就意味着该对象已经死亡,便可以在 instanceOopDesc的 Mark World 中将其标记为垃圾对象。
在根搜索算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

JVM中三种常见的垃圾收集算法?

标记-清除算法(Mark_Sweep)

首先标记出所有需要回收的对象,在标记完成后统一回收掉所有的被标记对象。

缺点:

  • 标记和清除的效率都不高。
  • 空间问题,清除后产生大量不连续的内存随便。如果有大对象会出现空间不够的现象从而不得不提前触发另一次垃圾收集动作。

复制算法(Copying)

他将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

优点:
解决了内存碎片问题。
缺点:
将原来的内存缩小为原来的一半,存活对象越多效率越低。


标记-压缩算法(Mark-Compact)

先标记出要被回收的对象,然后让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。解决了复制算法和标记清理算法的问题。

分代收集算法?

当前商业虚拟机的垃圾收集都采用“分代手机算法",其实就根据对象存活周期的不同将内存划分为几块,一般是新老年代。根据各个年代的特点采用最适当的收集算法。

垃圾收集器

如果说垃圾收集算法是方法论,那么垃圾收集器就是具体实现。连线代表可以搭配使用。

Stop The World

进行垃圾收集时,必须暂停其他所有工作线程,Sun将这种事情叫做"Stop The World"。

Serial收集器

单线程收集器,单线程的含义在于它会 stop the world。垃圾回收时需要stop the world,直到它收集结束。所以这种收集器体验比较差。

PartNew收集器

Serial收集器的多线程版本,除了使用采用并行收回的方式回收内存外,其他行为几乎和Serial没区别。
可以通过选项“-XX:+UseParNewGC"手动指定使用 ParNew收集器执行内存回收任务。

Parallel Scavenge

是一个新生代收集器,也是复制算法的收集器,同时也是多线程并行收集器,与PartNew不同是,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU 用于运行用户代码 的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。
他可以通过2个参数精确的控制吞吐量,更高效的利用cpu。

分别是:-XX:MaxCcPauseMillis 和 -XX:GCTimeRatio

Parallel Old收集器

Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。JDK 1.6中才开始提供。

CMS 收集器

Concurrent Mar Sweep 收集器是一种以获取最短回收停顿时间为目标的收集器。重视服务的响应速度,希望系统停顿时间最短。采用标记-清除的算法来进行垃圾回收。

CMS垃圾回收的步骤?
  • 初始标记(stop the world)
  • 并发标记
  • 重新标记(stop the world)
  • 并发清除
    • 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
    • 并发标记就是进行Gc Roots Tracing的过程。
    • 重新标记则是为了修正并发标记期间,因用户程序继续运行而导致的标记产生变动的那一部分对象的标记记录,这个阶段停顿时间一般比初始标记时间长,但是远比并发标记时间短。
    • 整个过程中并发标记时间最长,但此时可以和用户线程一起工作。
CMS收集器优点?缺点?

优点:
并发收集、低停顿

缺点:

  • 对cpu资源非常敏感。
  • 无法处理浮动垃圾。
  • 内存碎片问题。

G1收集器?

Garbage First 收集器是当前收集器技术发展的最前沿成果。jdk 1.6_update14中提供了 g1收集器。
G1收集器是基于标记-整理算法的收集器,它避免了内存碎片的问题。
可以非常精确控制停顿时间,既能让使用者明确指定一个长度为 M毫秒的时间片段内,消耗在垃圾收集上的时间不多超过N毫秒,这几乎已经是实时Java(rtsj)的垃圾收集器特征了。

G1收集器是如何改进收集方式的?

极力避免全区域垃圾收集,之前的收集器进行收集的范围都是整个新生代或者老年代。而g1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域,并且跟踪这些区域垃圾堆积程度,维护一个优先级李彪,每次根据允许的收集时间,优先回收垃圾最多的区域。从而获得更高的效率。

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

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

相关文章

海医大三院使用先进血管外科微创技术成功救治危重主动脉夹层患者

近日,上海东方肝胆外科医院血管外科周建教授团队采用主动脉弓分支型一体化移植物联合体外开窗技术,成功救治复杂危重主动脉夹层患者,为上海嘉定首例,彰显了上海东方肝胆外科医院血管外科的优势与特色。 患者谢先生,72岁,两周前突发剧烈胸背部撕裂样疼痛,休息后症状未能得到缓解…

量化交易:如何在QMT中运行Python策略并在VSCode中高效调试?

哈喽,大家好,我是木头左! 为何选择QMT和VSCode进行量化策略开发? 在量化交易的世界里,选择正确的工具与拥有优秀的策略同等重要。调用用Visual Studio Code(简称VSCode)或pycharm,方…

JAVA 大鱼吃小鱼小游戏

java实现大鱼吃小鱼,支持身份证防沉迷、账号密码、选择难度 放沉迷 登录 选择难度 游戏界面

【移除链表元素】python

目录 题目: 方法: 知识: 代码: 题目: 方法: 在头节点前增加一个虚拟头节点 知识: 链表中的每一个节点只包含当前值val和指向下一个next 代码: class Solution:def removeEle…

uniapp或微信小程序一些问题解决

1.按钮边框如何去除? 参考博主:微信小程序按钮去不掉边框_微信小程序button去掉边框-CSDN博客文章浏览阅读1k次。最近在学uni-app,顺便自己写个小程序。左上角放了个button,可边框怎么也去不掉…原来微信小程序的按钮要去掉边框要…

汽车IVI中控开发入门及进阶(二十一):DAB和FM 收音机

前言: 在过去的十年里,数字收音机对车载娱乐产生了重大影响。现在,几乎每辆新车都标配了这项技术,这也是我们60%以上的人收听收音机的方式。甚至有传言称,在不久的将来,将永久关闭调频发射机,使许多车载收音机过时。但一些相对年轻的汽车在工厂里仍然没有安装DAB,而且…

六西格玛培训:企业逆袭的秘密武器!——张驰咨询

为了提升企业的运营效率、产品质量和客户满意度,六西格玛培训成为了一个不可或缺的环节。以下是企业成功实施六西格玛培训的关键步骤: 一、清晰设定培训目标 首先,企业应明确六西格玛培训的具体目标,如提升产品质量、降低成本、…

java Web开发中采用Servlet登录验证,中文用户名始终提示“用户名密码错误”以及输出中文乱码问题

采用Servlet登录验证,中文乱码问题解决 在Java Web开发中,往往采用Servlet完成前后端直接的控制和处理,例如:用户登录验证功能。 在采用如下Servle源码t完成用户名登录验证时,只要用户名涉及中文,对于正确…

SpringBoot 之基础(一)

文章目录 SpringBoot 基础基本概念创建 SpringBoot 项目编码编写启动类写 Controller运行 / 测试properties 和 yml关闭 Spring banner日志spring-boot 默认的日志格式 解决 start.spring.io 不能访问不使用 spring boot 的 parent pom SpringBoot 基础 Spring Boot 是由 Pivo…

梳理清楚的echarts地图下钻和标点信息组件

效果图 说明 默认数据没有就是全国地图, $bus.off("onresize")是地图容器变化刷新地图适配的,可以你们自己写 getEchartsFontSize是适配字体大小的,getEchartsFontSize(0.12) 12 mapScatter是base64图片就是图上那个标点的底图 Ge…

2024年5月最新高德poi数据采集科普

曾几何时,个人注册高德开发者即可拥有每日一万次免费配额调用,现如今,个人每日只能调用100次,即使额外购买了配额300元/10万次,也会因为短时间大量采集被封号。要想稳定采集,恐怕只有购买商业授权5万/年&am…

kafka的安装与简单使用

下载地址:Apache Kafka 1. 上传并解压安装包 tar -zxvf kafka_2.13-3.6.2.tgz 修改文件名:mv kafka_2.13-3.6.2 kafka 2. 配置环境变量 sudo vim /etc/profile #配置kafka环境变量 export KAFKA_HOME/export/server/kafka export PATH$PATH:$KAFKA…

【Vue】v-if / v-show条件渲染指令

条件判断指令,用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是: v-show 作用: 控制元素显示隐藏(简单的显示隐藏) 语法: v-show "表达式" 表达式值为 tru…

如何评价GPT-4o?

GPT-4o:开启全新理解与生成语言的篇章 在近年来的AI发展中,GPT模型赫然矗立,在自然语言处理任务中刷新了人们的认知,一路从GPT-1演进到如今的GPT-4o。 从GPT-1到GPT-4,我们可以看到模型的层数和参数量在持续增长&…

解密网络流量监控:优化IT运维的利器

引言: 在当今数字化时代,网络流量监控是维护网络稳定与业务连续性的关键。作为一名资深网络工程师,我将分享一些关于网络流量监控的重要知识,并探讨如何在IT运维中运用这一工具优化网络性能,确保业务的顺畅进行。 1. 网…

基于Patroni+etcd+流复制搭建PostgreSQL高可用——筑梦之路

Patroni方案简介 Patroni是一个基于zk、etcd、consul等的pg ha模板,可以使用python来创建和定制高可用性解决方案。Patroni使用分布式key-value数据库作为数据存储,主节点故障时进行主节点重新选举。通过PG内置的流复制,支持同步和异步复制。…

k210数字识别 笔记2 (串口通信)

这个模型识别的还可以,离近点 识别率高达0.9 资源: 链接:https://pan.baidu.com/s/1D4ubJGMptqop1x_Nf8KqfQ?pwd1234 提取码:1234 一:报错解决 报错的意思应该是模型文件错误 原程序可以在sd卡运行,但…

Linux 服务器配置 SSH 服务登录失败处理

任务目标 配置 Linux 服务器ssh远程登录失败处理机制,防止黑客爆破服务器密码 操作步骤 备份原配置文件 $ sudo cp /etc/pam.d/sshd /etc/pam.d/sshd.bak $ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak安装 pam_tally2 统计登陆失败次数 # 对于Debia…

[C#]使用C#部署yolov8-cls的图像分类的tensorrt模型

【测试通过环境】 win10 x64 vs2019 cuda11.7cudnn8.8.0 TensorRT-8.6.1.6 opencvsharp4.9.0 .NET Framework4.7.2 NVIDIA GeForce RTX 2070 Super 版本和上述环境版本不一样的需要重新编译TensorRtExtern.dll,TensorRtExtern源码地址:TensorRT-CShar…

游泳时用什么耳机听歌好?精品榜前四游泳耳机揭秘,款款佳品!

游泳时用什么耳机听歌好?这无疑是众多水上运动爱好者的共同疑问。在享受游泳带来的清凉与畅快时,若能伴随着悦耳的音乐,无疑能让整个体验更加完美。然而,市面上的游泳耳机种类繁多,品质各异,如何选择一款既…