冲击大厂面试题总结【day1】-- jvm篇

每个人都有一个梦,加油让自己的青春不留遗憾!!!下文有不正确的地方或者有疑问可以评论互相讨论学习。

运行时数据区域

 

程序计数器

        程序计数器是一块很小的内存空间,它可以看作时当前线程所执行的字节码的行号指示器。在java虚拟机中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要这个计数器来完成。

        java虚拟机的多线程是通过轮流切换、分配处理器执行时间的方式来实现的。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,互不影响,所以程序计数器是线程私有的。如果正在执行的是本地方法,则这个计数器值应该为空(undefined)。此内存区域是唯一一个在《java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。

java虚拟机栈

        虚拟机栈也是线程私有的,它的生命周期与线程相同。每个方法执行时,java虚拟机都会同步创建一个栈帧用于存放局部变量表、操作数栈、动态链接、方法出口等信息。

局部变量

        局部变量表中存放的是基础数据类型(boolean、byte、char、short、int、float、long、double)及对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。局部变量保存在slot(变量槽)中,boolean、byte、char、short、int、float、reference和returnAddress 保存在一个solt(槽位)中,而long和double类型占用两个连续的solt。jvm会为局部变量表中的每一个solt都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。当一个实例方法被调用时,它的方法参数和方法体内部定义的局部变量会按照顺序被复制到局部变量表中的每一个solt上。如果当前栈帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排序。

        solt重复利用:栈帧中的局部变量表中的槽位是可以重复利用的,如果一个局部变量过了其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。如下图所示,变量b超出了它的作用范围,新增变量c就利用了过期变量b的solt槽。可以看出b 和 c的index索引都是2,也就说明一共用了三个存储空间。

 

操作数栈

        每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出的操作数栈,也被称为表达式栈。操作数栈在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈/出栈。

        操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

 

 

动态链接

        静态链接:在类加载-解析阶段,其中一部分符号引用直接解析为直接引用的过程被称为静态链接。也就是方法在程序编译期就可以确定调用版本,并且这方法的调用版本在运行期是不可改变的。可概括为:编译期可知、运行期不可变。符合上述条件的方法主要包含静态方法和私有方法两大类。

        动态链接:除了静态链接,有一部分方法引用在编译期无法确定正真的版本,只能在运行时确定下来。在运行期把符号引用转换成直接引用的过程被称为动态链接。

        静态分派:所有依赖静态类型来定位执行版本的分派动作被称为静态分派。静态分派的经典应用是方法重载。

        动态分派:在运行期才能确定接收者的实际类型,从而将符号引用解析成直接引用的过程被称为动态分派,也是重写的本质。

本地方法栈

        本地方法栈与虚拟机栈发挥的作用很相似,其区别就是虚拟机栈用作与虚拟机执行的java方法,而本地方法栈作用于虚拟机执行到的本地方法。(虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据机构没有任何强制性的规定,因此具体的虚拟机可以根据需求自由实现它,hot-spot直接将本地方法栈和虚拟机栈合二为一)

java堆

        java堆是被所有线程共享的一块内存区域,在虚拟机启用时创建。此内存区域的唯一目的就是存放对象实例,java中"几乎"所有的对象实例都在这里分配内存。由于即使编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换等优化手段导致一些对象的实例出现了在栈上分配的情况,使得java对象实例都分配在堆上也渐渐变得不是那么绝对。

        现代大部分垃圾收集器都是基于分代收集理论设计的,内存空间被划分为年轻代(eden、from survivor、to survivor)、老年代、永久代等内存区域,方便内存管理回收。但是这种区域划分的方式仅仅是一部分垃圾收集器的设计风格而已,并非java虚拟机具体实现的固有内存布局,更不是《java虚拟机规范》里对java堆的进一步细致划分。也就是说堆的分代设计并非规范。在十年之前(以G1收集器的出现为分界线),之后除了分代设计也出现了其他的设计。但是无论如何划分,都不会改变java堆中存储内容的共性,存储的都是对象的实例,将java堆细分的目的只是为了更好的回收内存和更快的分配内存。

方法区

        方法区和java堆一样,是各个线程共享的内存区域,它用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。HotSpot虚拟机(其他虚拟机是没有永久代的概念的)在jdk8以前方法区由永久代来实现的(永久代是占用了堆的内存空间的,当初设计的目的是为了方法区也能够更好的就行内存回收,但是经过后来的验证,反映出这个设计并非一个很好的设计方案,这种设计增加堆内存溢出的风险)。因此HotSpot jdk8以后完全废除了永久代的概念,使用云空间来代替,用空间使用的是本地内存(Native Memory)来存储数据的。

        HotSpot开发团队在JDK6就已经有了放弃永久代,改为采用本地内存来实现方法区的计划了,到了JDK7的hotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出,而到了JDK8之后完全废除了永久代的概念,使用本地内存实现的元空间来实现方法区。

运行时常量池

        运行时常量池是方法区的一部分。方法区Class文件中也存在一个Class文件常量池。Class文件常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

        对于运行时常量池《java虚拟机规范》并没有做任何细节的要求,不同提供商实现的虚拟机可以按照自己的需要来实现这一内存区域,不过一般来说,除了保存Class文件中描述的符号引用外,还会把符号引用翻译成直接引用也存储在运行时常量池中。

        运行时常量池相对于Class文件常量池的另外一个重要的特征是具有动态性,java语言并不要求常量一定只能在编译期才能产生,也就是说并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,在运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的就是String类中的intern()方法。

        运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

直接内存

        直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《java虚拟机规范》中定义的内存区域。但是这个内存区域也有可能导致OutOfMemoryError异常出现。

        直接内存的使用,jdk1.4新加入了NIO类,引入了一种基于通道(channel)与缓存区(buffer)的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和native堆中来回复制数据。

        虽然本机直接内存的分配不会受到java堆大小的限制,但是肯定会受到本机总内存大小以及处理器寻址空间的限制,一般开发人员在配置虚拟机参数的时,会根据实际内存去设置-Xmx等参数信息,但是经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制,从而导致动态扩展时会出现OutOfMemoryError异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值