JVM知识点 2 运行时数据区
运行时数据区
运行时数据区内部结构
程序计数器
程序计数器(也叫PC寄存器):寄存器存储指令相关的现场信息,cpu只有把数据装载到寄存器才能运行。jvm中的pc寄存器是对物理pc寄存器的一种抽象模拟。pc寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码,由执行引擎读取下一条指令。
常见面试题
1.为什么要使用pc寄存器记录当前的线程执行地址呢?使用pc寄存器存储字节码指令地址有什么用?
答:因为cpu需要不停的切换各个线程,这时候切换回来以后就需要知道从哪里继续执行。JVM字节码解释器就需要通过改变pc寄存器的值来明确下一条应该执行什么样的字节码指令。
2.pc寄存器为什么被设定为线程私有?
答:由于cpu会不停的做任务切换,这样必然会导致经常中断或恢复,为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的办法就是为每一个线程都分配一个pc寄存器,这样一来各个线程之间可以独立计算,互不干扰。
虚拟机栈
虚拟机栈出现的背景:由于跨平台性的设计,java指令都是根据栈来设计的,不同平台cpu架构不同,所以不能设计为基于寄存器的。他的优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多指令。
什么是java虚拟机栈?
java虚拟机栈,早期也叫java栈,每个线程在创建时,都会创建一个java虚拟机栈,其内部保存一个个栈帧对应着一次次的方法调用,他是线程私有的。
jvm直接对栈的操作只有两个,就是对栈帧的压栈和出栈,遵循“先进后出,后进先出”原则
虚拟机栈的生命周期和线程一致。
作用:主管java程序的运行,他保存方法的局部变量,部分结果,并参与方法的调用和返回。
栈中存储的是什么?
1.每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在的
2.在这个线程上每一个正在执行的方法都对应一个栈帧
3.栈帧是一个内存区块,是一个数据集,维系方法执行中的各种数据信息。
栈帧中的内部结构
- 局部变量表(栈中调优 和局部变量表有关)
- 操作数栈
- 动态链接(或运行时常量池的引用)
- 方法返回地址(或方法正常退出或者异常退出的定义)
- 一些附加信息
栈中存常见异常
Java虚拟机允许栈的大小是固定的或者是动态变化的,当虚拟机栈容量固定不变时,如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量,java虚拟机会抛出StackOverflowError异常,如果栈是可以动态扩展的在尝试扩展时无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那么虚拟机会抛出OutOfMemoryError异常。
栈顶缓存技术
由于操作数是存储在内存当中的,因此频繁的执行内存的读写操作必然会影响执行速度。而栈顶缓存技术就是将栈顶元素全部缓存在物理cpu的寄存器中,降低读写次数,提高执行引擎的执行效率。
虚拟机栈常见面试题
-
举例栈溢出的情况?
答:StackOverflowError和OutOfMemoryError -
调整栈大小就能保证不出现溢出么?
答:不能 -
分配的栈内存越大越好么?
答:不是,会挤占其他内存空间 -
垃圾回收是否会设计到虚拟机栈?
答:不会,只会涉及到堆和方法去 -
方法中定义的局部变量是否线程安全?
答:具体情况具体分析,得看这个数据是被单个还是多个线程操作,单线程操作时安全的,多线程操作不考虑同步机制问题会存在线程安全问题。
本地方法栈
- java虚拟机栈用来管理java方法的调用,而本地方法栈用于管理本地方法的调用。
- 本地方法栈也是线程私有
- 允许被实现成固定或者时可拓展的内存大小
- 本地方法时C语言实现的
- 具体做法是在虚拟机栈中登记本地方法,在Execution Engine执行时加载本地方法库。
堆
堆的核心概述
- 一个jvm实例只存在一个堆内存,堆也是java内存管理的核心区域
- java堆区在jvm启动时就被创建,其空间大小也被确定,是jvm管理的最大的一块内存空间。
– 堆内存的大小是可以调节的 - java虚拟机规范规定,堆可以处于物理上不连续的内存空间中,但在逻辑上他应该被视为连续的
- 所有的线程共享java堆,在这里还可以划分线程私有的缓冲区
- 所有的对象实例以及数组都应当在运行时分配到堆上
- 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。
- 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候被移除
- 堆,是GC(垃圾收集器)执行垃圾回收的重点区域
堆中内存结构
jdk1.7及之前 年轻代 老年代 永久区
jdk1.8及之后 年轻代 老年代 元空间
设置堆内存大小和OOM
堆空间大小的设置
- java堆区用于存储java对象实例,那么堆的大小在jvm启动时已经设定好了,可以通过选项 -Xmx(最大内存)和 -Xms(起始内存) 来进行设定
- 一旦堆中的内存大小超过-Xmx指定的最大内存时,将会抛出OOM异常
- 通常会将 -Xmx 和 -Xms 这两个参数设置为相同的值,其目的时为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆的大小,从而提高性能。
- 默认情况下,初始内存大小为物理电脑内存大小的64分之一,最大内存为物理电脑内存大小的4分之一。
Minor GC ,Major GC, Full Gc的区别
jvm在进行GC时,并非每次都对上面三个内存(新生代,老年代;方法区)区域一起回收的,大部分时候回收都是指新生代。
针对HotSpot VM的实现,它里面的GC 按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(Full GC):
- 部分收集:不是完整收集整个java堆的垃圾收集。其中又分为:
– 新生代收集(Minor GC / Young GC):只是新生代的垃圾收集
– 老年代收集(Major GC / Old GC):只是老年代的垃圾收集。
注意1.目前只有CMS GC会有单独收集老年代的行为。2. 很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
–混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集(目前只有G1 GC 会有这种行为) - 整堆收集(Full GC):收集整个java堆和方法区的垃圾收集。
方法区
- 方法区和堆一样,是各个线程共享的内存区域
- 方法区在jvm启动时被创建,并且他的实际的物理内存空间中和java堆区一样都可以不连续的。
- 方法区的大小跟堆空间一样,可以选择固定大小或者可拓展
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误
- 关闭jvm就会释放这个区域的内存。
方法区存储的是什么?
它用于存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等。
方法区的垃圾回收主要分为两部分内容:常量池中废弃的常量和不在使用的类型
常见面试题
百度:说一下jvm内存模型,有哪些区,分别是做什么的?
蚂蚁金服:java8的内存分代改进,jvm内存分几个区,每个区的作用是什么?
- 一面:jvm内存结构,栈和堆的区别,堆的结构,为什么有两个survior区?
- 二面:eden和survior的比例分配
小米:jvm内存分区,为什么要有新生代和老年代
字节跳动:讲讲jvm运行时数据库区,什么时候对象会进入老年代
京东:jvm内存为什么要分为新生代老年代和持久代。新生代中为什么要分eden和survior区
天猫:jvm内存模型,java8以后做了什么修改
美团:jvm永久代中会发生垃圾回收么?