深入浅出 Java 中 JVM 内存管理

简介: Java岗位面试,JVM是对程序员基本功考察,通常会问你对JVM了解吗?可以分几部分回答这个问题,首先JVM内存划分 | JVM垃圾回收的含义 | 有哪些GC算法 以及年轻代和老年代各自特点等等。

Java岗位面试,JVM是对程序员基本功考察,通常会问你对JVM了解吗?


可以分几部分回答这个问题,首先JVM内存划分 | JVM垃圾回收的含义  |  有哪些GC算法  以及年轻代和老年代各自特点等等。


1) JVM内存划分:


①  方法区 (线程共享)  常量  静态变量  JIT(即时编译器)编译后代码也在方法区存放


② 堆内存(线程共享) 垃圾回收的主要场地


③  程序计数器  当前线程执行的字节码的位置指示器


④   Java虚拟机栈(栈内存) :保存局部变量,基本数据类型以及堆内存中对象的引用变量


⑤ 本地方法栈  (C栈):为JVM提供使用native方法的服务


通过这幅图了解一下


image.png


JDK 1.8同JDK 1.7 最大的区别是:元数据取代了永久代.元空间的本质和永久代类似,都是对JVM规范中的方法区的实现.其元空间和永久代之间的最大区别在于:元数据空间不在虚拟机中,而是在本地内存中


详细了解一下各个部分


01)程序计数器(PC寄存器)


程序计数器的定义: 程序计数器是一块较小的内存空间,是当前线程正在执行的哪一条字节码指令的地址,若当前线程正在执行的是一个本地方法,那么此时程序计数器为Undefined


程序计数器的作用:


字节码解释器通过改变程序计数器来依次获取指令,从而实现代码的流程的控制


在在多线程情况下,程序计数器记录的是当前线程执行的执行的位置,从而当线程切换回来时,就知道上次线程执行到哪了


程序计数器的特点


是一块较小的内存空间


线程私有,每个线程都有自己的程序计数器


生命周期:随着线程的创建而创建,随着线程的销毁而销毁


是一个唯一不会出现的OutOfMemoryError的内存区域


02)Java虚拟机栈


定义: 描述Java方法运行过程的内存模型


Java虚拟机栈会为每一个即将运行的Java方法创建一块叫做"栈帧"的区域,用于存放该方法运行过程中的一些信息,如  局部变量表  /操作数栈  /动态链接 /方法出口信息  .............


image.png


压栈出栈过程:


当方法运行过程中需要创建局部变量时,就将局部变量的值存入栈帧的局部变量表中


Java虚拟机栈的栈顶是当前正在执行的活动栈,也就是当前正在执行的方法,PC寄存器也会指向这个地址,只有这个活动的栈帧的本地变量可以被操作数栈操作,当前这个栈帧中调用另一个方法,与之对应的额栈帧又会被创建,新创建的栈帧压入栈顶,变成当前的活动栈帧,方法结束后,当前栈帧的返回值变成新的活动栈帧的中的操作数栈的一个操作数,如果没有返回值,那么新的活动栈帧中操作数栈的操作数没有变化


由于Java虚拟机栈是线程对应的,数据不是共享的,因此不用关心数据一致性问题,也不会存在同步锁的问题


特点


局部变量表随着栈帧的创建而创建,他的大小在编译时确定,创建时只需分配事先规定的大小即可,在方法运行的过程中,局部变化表的大小不会发生变化


Java虚拟机栈会出现两种异常:StackOverFlowError和OutOfMemoryError


StackOverFlowError若Java虚拟机栈的大小不允许动态扩展,那么当前线程请求的栈的深度超过当前的Java虚拟机栈的最大深度是,就会抛出此异常


OutOFMemoryError,若允许动态扩展,那么当前线程的请求的栈内存用完了,无法再动态扩展时,抛出此异常


Java虚拟机栈也是线程私有,随着线程创建而创建,随着线程的结束而销毁


03)本地方法栈(C栈)


定义: 是为了JVM运行native方法准备的空间,由于很多native方法都是用C语言实现的,所以通常又叫C栈,它与Java虚拟机栈实现的功能类似,只不过本地方法栈描述本地方法运行过程的内存模型


栈帧变化过程:


本地方法被执行时,在本地方法栈也会创建一块栈帧,用于存放该方法的局部变量表  /操作数栈 /动态链接 /方法出口等信息; 方法结束后,相应的栈帧也会出栈,并释放内存空间.也会抛出StackOverFlowError和OutOfMemoryError异常


04) 堆


定义: 堆是用来对象的内存空间,几乎所有的对象都存储在堆中


特点:


线程共享,整个Java虚拟机只有一个堆,所有线程都访问同一个堆.


在虚拟机启动时创建


是垃圾回收的主要场地


进一步可分为:新生代(Eden区 From Survior To Surviror)  老年代


不同的区域存放的不同生命周期的对象,这样可以根据不同区域使用不同的垃圾回收算法,更具有针对性. 堆的大小也可以固定也可以扩展,对于主流的虚拟机,堆大小可扩展的,因此当线程请求分配的内存,但堆已满,且内存已无法再扩展,就抛出OutOfMemoryError异常


05)方法区


定义: Java虚拟机规范中定义方法区是堆的一个逻辑部分,方法区存放以下信息  已被虚拟机加载的类信息  /常量  /静态变量 /即时编译后代码


特点:


线程共享.方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享,整个虚拟机中只有一个方法区


永久代 方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,把方法区称为"永久代"


内存回收的效率低.方法区中的信息一般需要长期存在,回收一遍只有少量信息无效.主要回收的目标是: 对常量池的回收;对类型的卸载


Java虚拟机规范l对方法区的要求比较宽松,和堆一样,允许固定大小.也允许动态扩展,还允许不实现垃圾回收


运行时常量池:


方法区中存放:类信息  常量  静态变量  即时编译器变编译后代码.常量就存放在运行时常量池中.当类被Java虚拟机加载后,.class文件中的常量就存在方法区的运行常量池,而且在运行期间,可以向常量池中添加新的常量,如String类的intern()方法就能在运行期间向常量池中添加字符串常量


06) 直接内存(堆外内存)


直接内存是除Java虚拟机之外的内存,但有可能被Java使用


操作直接内存:


在NIO中引入了一种基于通道和缓存的IO方式,他可以调用本地方法的直接分配Java虚拟机之外的内存,然后通过一个存储在堆中的DirectByteBuffer对象直接操作该内存,而无需将外部内存中数据复制到堆中再进行操作,从而提高数据操作的效率,直接内存的大小不受Java虚拟机,也会抛出OutOfMemoryError异常


直接内存和堆内存比较:


直接内存申请空间耗费更高的性能


直接内存读取IO的性能优于普通的堆内存


直接内存的作用链:本地IO-->直接内存-->本地IO


堆内存的作用链:本地IO-->直接内存-->非直接内存-->直接内存--->本地IO


服务器管理员在配置虚拟机参数时,会根据实际内存设置 -Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存,从而导致动态扩展时出现OutOFMemoryError


2)类似 -Xms  -Xmn这些参数的含义


堆内存分配


① : JVM初始分配的内存由-Xms指定,默认是物理内存的1/64


②:  JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4


③: 默认空余堆内存小于40%时,JVM就会增加堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制


④: 因此服务器一般设置-Xms  -Xmx相等以避免在每次GC后调整堆大小. 对象的堆内存由成为垃圾回收器的自动内存管理系统回收


非堆内存分配:


①:JVM使用-XX:PermSize 设置非堆内存的初始值,默认物理内存的1/64;


② :由XX:MaxPermSize设置设置最大非堆内存的大小


③: -Xmn2G :设置年轻代的大小为2G


④ :-XX:SurvivorRatio ,设置年轻代中Eden区与Survivor区的比值


3)垃圾回收的算法有哪些?


① 引用计数法:原理是在此对象有个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只收集计数为0的对象.此算法的最致命的无法处理循环引用的问题


②: 标记-清除 :此算法分两个阶段,第一阶段从引用的根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除,此算法需要暂停应用,同时产生内存碎片


③: 复制算法 此算法把内存划分为两个相等的区域,每次只使用一个区域,垃圾回收时,遍历当前使用的区域,把正在使用的对象复制到另一个区域中每次算法每次只处理正在使用的对象,因此复制的成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现"碎片问题",此算法的缺点也很明显,需要两倍的内存空间


④: 标记-整理:此算法结合了"标记-清除"和:复制算法的两个的优点,也是分两个阶段,第一个阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除未标记的对象并且把存活的对象"压缩"到堆的其中一块,按顺序排放,,此算法避免"标记-清除"的碎片问题,同时也避免"复制"的空间问题


4)root搜索算法中,哪些可以作为root?


被启动类(bootstrap加载器)加载的类和创建的对象


JavaStack中引用的对象(栈内存中引用的对象)


方法区中静态引用


相关文章
|
3月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
373 3
|
4月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
2月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
95 4
|
2月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
267 5
|
2月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
2月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
3月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
577 17
|
4月前
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
236 1
|
4月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
352 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
4月前
|
存储 监控 算法
Java垃圾回收机制(GC)与内存模型
本文主要讲述JVM的内存模型和基本调优机制。

热门文章

最新文章