阿里P8大神讲解——Java,JVM内存模型
在Java程序界流行着一种默认的说法叫”黄金5年”,也就是一个程序员从入职的时间开始算起,前五年的选择直接影响着整个职业生涯发展方向和薪资走向。
如何走好这5年很关键,如何彻底从一个菜鸟蜕变成,可以以不变应万变的职业大牛,这是一个涉及到自身专业知识储备和选择的大难题。
很简单,既然选择了Java技术栈,必须要深入学习。
如何保持高效?我做到了这两点:坚持学习和向大神学习。如果你想在Java道路上不断精进,我也给大家分享,当时我在进阶的时候加入的大神群,里面有很多大厂经验的大神以及各个版块的资料可以免费领取,有需要的自己进群就好了。
对于Java开发者来说,想把自身能力提升到更高层次,某些JVM相关知识应该是优先级很高的。比如说GC策略,JVM调优。
就我之前在工作中遇到的情况来看:接触了几年Java,现在做Java Web开发,只是关注Java本身知识,对JVM关注得比较少。
实际,学习JVM对理解Java有很大的帮助,而且现在待遇好的公司,面试时基本都会考核JVM相关问题,如果你不会,面试通过的机会很渺茫。
所以,掌握JVM很有必要。
根据JVM规范,JVM内存共分为程序计数器、虚拟机栈、本地方法栈、堆和方法区五个部分,如下图所示:
程序计数器
它是当前线程执行字节码的行号指示器。
在多线程中,为了让每个线程切换回来后能够恢复原来执行的指令,就需要为每个线程启动一个PC计数器,这些计数器之间是互补影响的,因为程序计数器和栈一样都是线程私有的。
当然,程序计数器是JVM唯一一个不会出现内存溢出的组件。
什么是程序计数器?
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
------ 摘自《深入理解JAVA虚拟机》
特点:
线程私有
JVM规范中唯一没有规定OutOfMemoryError情况的区域
如果正在执行的是Native 方法,则这个计数器值为空
首先,为什么是线程私有?
Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,也就是说,在同一时刻一个处理器内核只会执行一条线程,处理器切换线程时并不会记录上一个线程执行到哪个位置,所以为了线程切换后依然能恢复到原位,每条线程都需要有各自独立的程序计数器。
为什么没有规定OutOfMemoryError?
如上文,程序计数器存储的是字节码文件的行号,而这个范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存。
为什么执行Native方法,值为空?
Native方法大多是通过C实现并未编译成需要执行的字节码指令,也就不需要去存储字节码文件的行号了。
如果执行Native方法时,程序计数器不存储值,那么线程切换后如何恢复原位继续执行?
这里的“pc寄存器”(即程序计数器)是在抽象的JVM层面上的概念——当执行Java方法时,这个抽象的“pc寄存器”存的是Java字节码的地址。实现上可能有两种形式,一种是相对该方法字节码开始处的偏移量,叫做bytecode index,简称bci;另一种是该Java字节码指令在内存里的地址,叫做bytecode pointer,简称bcp。对native方法而言,它的方法体并不是由Java字节码构成的,自然无法应用上述的“Java字节码地址”的概念。所以JVM规范规定,如果当前执行的方法是native的,那么pc寄存器的值未定义——是什么值都可以。
上面是JVM规范所定义的抽象概念,那么实际实现呢?
Java线程总是需要以某种形式映射到OS线程上。映射模型可以是1:1(原生线程模型)、n:1(绿色线程 / 用户态线程模型)、m:n(混合模型)。
以HotSpot VM的实现为例,它目前在大多数平台上都使用1:1模型,也就是每个Java线程都直接映射到一个OS线程上执行。此时,native方法就由原生平台直接执行,并不需要理会抽象的JVM层面上的“pc寄存器”概念——原生的CPU上真正的PC寄存器是怎样就是怎样。就像一个用C或C++写的多线程程序,它在线程切换的时候是怎样的,Java的native方法也就是怎样的。
2. 虚拟机栈
存放了每一个线程的当前状态,每一个线程都有一个自己的栈,而栈中存放了以下数据组成的一个个栈帧:操作数、局部变量表、动态链接、返回地址。
需要注意的是,栈中只存引用或者基本类型,而且线程不共享(并没有指内部的优化动作)。
虚拟机栈是一个后入先出的栈。栈帧是保存在虚拟机栈中的,栈帧是用来存储数据和存储部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素。如下图所示:
栈帧
上述内容已对栈帧做了大致介绍,接下去仔细描述栈帧中的操作数栈,动态连接,方法返回地址和一些额外的附加信息。 如下图所示:
3. 本地方法栈
本地方法栈与虚拟机栈的功能非常相似,区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机执行Native方法服务。
有的虚拟机并不会区分本地方法栈和虚拟机栈,比如Sun HotSpot虚拟机直接将两个合二为一。
均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。
不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。如何去服务native方法?native方法使用什么语言实现?怎么组织像栈帧这种为了服务方法的数据结构?虚拟机规范并未给出强制规定,因此不同的虚拟机实可以进行自由实现,我们常用的HotSpot虚拟机选择合并了虚拟机栈和本地方法栈。
4. 堆
堆区是JVM中占地最大的区域,是被所有线程共享的内存区域。存在的唯一目的就是存放对象实例,几乎所有的对象实例都在这里进行分配内存。
JVM规范中规定堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。并且可以通过-Xmx和-Xms来扩展堆的内存大小,如果在堆中没有足够的内存为实例分配,并且堆也无法在扩展时,就会报OutOfMemoryError异常。
堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。
在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
例子:
这是一个最大堆,,因为每一个父节点的值都比其子节点要大。10 比 7 和 2 都大。7 比 5 和 1都大。
根据这一属性,那么最大堆总是将其中的最大值存放在树的根节点。而对于最小堆,根节点中的元素总是树中的最小值。堆属性非常有用,因为堆常常被当做优先队列使用,因为可以快速地访问到“最重要”的元素。
注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。–唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。
5. 方法区
用于储存已被虚拟机加在的类信息、常量、静态变量、即时编译器编译后的代码。
运行时常量池也是方法去的一部分,比如String w=“hello”;中,hello就被放在了方法区里,方法区是线程共享的。
有一点要注意,JDK1.8使用元空间MetaSpace替代方法去,元空间并不在JVM中,而使用本地内存。
在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来的。类(静态)变量也存储在方法区中。
jvm实现的设计者决定了类型信息的内部表现形式。如,多字节变量在类文件是以big-endian存储的,但在加载到方法区后,其存放形式由jvm根据不同的平台来具体定义。
jvm在运行应用时要大量使用存储在方法区中的类型信息。在类型信息的表示上,设计者除了要尽可能提高应用的运行效率外,还要考虑空间问题。根据不同的需求,jvm的实现者可以在时间和空间上追求一种平衡。
因为方法区是被所有线程共享的,所以必须考虑数据的线程安全。假如两个线程都在试图找lava的类,在lava类还没有被加载的情况下,只应该有一个线程去加载,而另一个线程等待。
方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。同样方法区也不必是连续的。方法区可以在堆(甚至是虚拟机自己的堆)中分配。jvm可以允许用户和程序指定方法区的初始大小,最小和最大尺寸。
方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,一些类也会成为垃圾。jvm可以回收一个未被引用类所占的空间,以使方法区的空间最小。
对于程序员来说,JVM的重要性不言而喻,只有基础和实操都兼备的人,才能在行业中立足。
阿里P8大神讲解——Java,JVM内存模型相关推荐
- java jvm内存模型_Java(JVM)内存模型– Java中的内存管理
java jvm内存模型 Understanding JVM Memory Model, Java Memory Management are very important if you want t ...
- Java JVM内存模型
简述JVM内存模型 线程私有的运行时数据区: 程序计数器.Java 虚拟机栈.本地方法栈. 线程共享的运行时数据区:Java 堆.方法区. 简述程序计数器 程序计数器表示当前线程所执行的字节码的行号指 ...
- 阿里P8大神Frank:这4道面试题我一定会问,答好3题P6稳,全部OK考虑P7
本文作者:Frank杨逍,阿里P8高级技术专家,面试总计超过500+候选人. 裁员阴影下的互联网圈目前一片哀鸿遍野,所以居安思危.有备无患总是没错的. 若考虑跳槽,首推阿里.面试相对简单(面经多),J ...
- Java JVM内存模型(运行时数据区域)详解
详细介绍了JVM运行时数据区域,包括方法区.堆空间.栈空间.本地方法栈.程序计数器.常量池.直接内存.字面量.符号引用.直接引用. Java程序在运行时,需要在内存中的分配空间.为了提高运算效率,ja ...
- 阿里P8大神给予迷茫的程序员一些中肯建议,不要再虚度光阴了
最近好多人私信问我,该怎样才能成为高薪架构师,还有一个就对当前的状态感到迷茫,我在此做一个简单的说明,或者对迷茫中的你来说有些许帮助. 当前你感到迷茫吗? 有很多人对现在的工作感到很没意思,因为当前的 ...
- 大神讲解Java for循环的几种用法
本文非常适合初学Java的程序员,主要是来了解一下Java中的几种for循环用法,分析得十分详细,一起来看看. J2SE 1.5提供了另一种形式的for循环.借助这种形式的for循环,可以用更简单地方 ...
- 阿里P8大神:30岁还靠简历找工作的都是垃圾,一律不要……
如果说回望一下2019年,不得不说这一年大家的面试经历都别是一番滋味在心头! 最近在互联网匿名社区看到这样一个帖子:员工去大公司面试被"秒拒",面试官:30岁还靠简历找工作的都是垃 ...
- java 内存模型面试_Java面试- JVM 内存模型讲解
经常有人会有这么一个疑惑,难道 Java 开发就一定要懂得 JVM 的原理吗?我不懂 JVM ,但我照样可以开发.确实,但如果懂得了 JVM ,可以让你在技术的这条路上走的更远一些. JVM 的重要性 ...
- 阿里P8架构师谈:JVM的内存分配、运行原理、回收算法机制
不管是BAT面试,还是工作实践中的JVM调优以及参数设置,或者内存溢出检测等,都需要涉及到Java虚拟机的内存模型.内存分配,以及回收算法机制等,这些都是必考.必会技能. JVM内存模型 JVM内存模 ...
最新文章
- 温控自动烘焙系统的研究与实现
- NB-IoT与eMTC差异全解析
- java线程的创建与执行_Java多线程的创建和运行
- mybatis 取查询值_Mybatis --- 映射文件、参数处理、参数值的获取、select元素
- 乐高科技系列搭建指南 pdf_近30年十辆乐高科技系列摩托车回顾_积木
- 【译】WebSocket协议第四章——连接握手(Opening Handshake)
- php插件改名,自制functions.php文件or插件,防止升级或更换主题时被替换
- 记Ubuntu20.04搭建jekyll博客+github环境踩坑-终结版
- 8位模型计算机设计与仿真
- 加拿大计算机科学薪酬,加拿大最好找工作及薪酬最高的十大专业介绍
- SQL简体繁体转换函数代码
- nvidia-patch解除nvidia消费级显卡编码并发数量限制操作记录
- JavaScript面向对象实现-坦克大战(附前端全套学习路线)
- 【参赛作品66】快速搭建一套openGauss主备高可用集群
- web端常见导航设计
- Laravel文档梳理6、响应
- 第09章_性能分析工具的使用
- Shell脚本 单引号、双引号和反引号的区别
- 为什么你总是被骗呢?
- 如何使用TensorFlow实现卷积神经网络