JVM运行时数据区
五部分 及作用
(按照内存的小–>大)
PC寄存器
(1)PC寄存器存放每个线程的下一行指令
PC寄存器(程序计数器):用来存储指向下一条指令的地址,即将要执行的指令代码。
由执行引擎读取下一条指令。
特性:
-
它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域
-
在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
-
任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。
-
程序计数器会存储当前线程正在执行的Java方法的JVM指令地址,若在执行native方法,则是未指定值(undefined)
为什么使用PC寄存器,优点?
- 我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?
- 为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
虚拟机栈
(2)jvm栈每个线程对应一个栈,栈中存放栈帧,每个栈帧对应一个方法
栈太小:递归,大量循环,很容易OOM
栈太大:20 ,5 启动线程数量会变小
-Xss设置栈内存的大小,设置的栈的大小决定了函数调用的最大深度(配置每一个JVM的内存)
- 每一条Java虚拟机线程都有自己私有的Java虚拟机栈,这个栈与线程同时创建,也就是说,虚拟机栈的生命周期跟线程是一样的
- 每个方法在执行的同时都会创建一个栈帧(Stack
Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
例如:递归
main方法中调用main方法
例main调用m1 ,m1调用m2,m2调用m3
main m1 m2 m3 m3执行完出栈,继续执行m2打印1句话,m1,main
栈帧内部结构
局部变量表
store
操作数栈
push、load、add
动态链接
方法出口
常见面试题
1、举例栈溢出的情况
比如一个栈是大小是固定了的,当你调用的方法太多,超出了大小限制则会造成StackOverflowError。
还有就是栈的大小可以动态改变,用-Xss改变栈的大小,当尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个outOfMemoryError异常。
2、调整栈大小,能保证不出现溢出吗?
不能,如果是死循环,不管栈的大小是多少都会出现溢出
3、分配的栈内存越大越好吗?
不是,栈变大了内存中的剩余空间会变小。会挤压别的结构的生存空间
4、垃圾回收是否会涉及到虚拟机栈?
不会。因为虚拟机栈是个栈,只存在进栈出栈这两个操作
5、局部变量是否线程安全?
具体情况具体分析
如果只有一个线程才可以操作此数据,则必是线程安全的。
如果有多个线程操作此数据,则此数据是共享数据。如果不考虑同步机制的话,会存在线程安全问题。
本地方法栈
本地方法栈(Native Method Stacks)与Java虚拟机栈作用是非常相似的,只不过他是为虚拟机使用本地(Native)方法而服务
方法区(GC重点)
方法区,元空间是jdk8方法区的实现,永久代PerGen(jdk7不看,资料古老)
元数据区大小可以使用参数-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定
方法区内部结构
方法区存储信息主要:类型信息,域(Field)信息,方法(Method)信息,常量,静态变量,即时编译器编译后的代码缓存
1、类型信息
对每个加载的类型(类class、接口、枚举、注解),jvm必须在方法区存储以下类型信息
(1)类型的完整有效名称(全名=报名.类名)
(2)类型直接父类的完整有效名(接口和java.lang.Object,没有父类)
(3)类型的修饰符(public,abstract,final的某个子集)
(4)类型直接接口的一个有序列表
2、域(Field)信息
(1)保存类型的所有域的相关信息以及域的声明顺序
(2)域的相关信息:域名称,域类型,域修饰符(public,private,protected,static,final,volatile,transient)
3、方法(Method)信息
jvm保存所有方法的以下信息,同域信息一样的包括声明顺序
(1)方法名称
(2)方法返回参数(或者void)
(3)方法参数的数量和类型(按顺序)
(4)方法的修饰符(public,private,protected,static,final,synchronized,native,abstract)
(5)方法的字节码,操作数栈、局部变量表及大小(abstract和native除外)
(6)异常表(abstract和native除外),每个异常处理的开始位置,结束位置,代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引。
4、常量池
一个有效的字节码文件除了包含类的版本信息,字段,方法以及接口等描述信息外,还包含一项信息那就是常量池,包含各种字面量(数量值,字符串值)和对类型(类),域和方法的符号引用。
常量池,可以看作是一个表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等类型
(1)方法区,内部包含了运行时常量池
(2)字节码文件,内部包含了常量池
常量池的作用
一个java源文件的类,接口,编译后产生一个字节码文件。而java中字节码需要数据支持,通常这种数据会很大以至于不能直接存储在字节码里,换一种方式,可以存储到常量池里。
运行时常量池
(1)运行时常量池是方法区的一部分,常量池表是class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载存放到方法区的运行时常量池中。
(2)运行时常量池创建时机:在加载类和接口到虚拟机后,就会创建对应的运行时常量池
(3)jvm为每一个已加载的类型(类或者接口)都维护一个常量池,池中的数据项和数组项类似,使用索引访问
(4)运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换成真实地址
(5)运行时常量池类似于传统编程语言的符号表,但是它所包含的数据比符号表更加丰富
(6)当创建类或者接口的运行时常量池,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则jvm会抛出OutOfMemoryError异常。
(7)运行时常量池具备动态性,比如使用String类的intern方法加入运行时常量池中
String ss=“hello”;
ss.intern();
堆(GC重点)
堆:Java大部分new出来的对象放在堆空间
堆分为新生代和老年代,默认比例1:2
配置新生代与老年代在堆结构的占比:使用jinfo -flag NewRatio 进程id 查看
默认 -XX:NewRatio=2,表示新生代与老年代1:2
可以修改 -XX:NewRatio=4,表示新生代与老年代1:4
XX:NewSize和-Xmn(-XX:MaxNewSize):新生代的初始内存和新生代的最大内存
几乎所有的 Java 对象都是在 Eden 区被 new 出来的。
绝大部分的 Java 对象的销毁都在新生代进行
什么时候触发GC
Eden满了
堆-对象分配过程
- new 的对象先放在 Eden 区,此区有大小限制。
- 当 Eden 区的空间填满时,程序有需要创建新对象,JVM 的垃圾回收器将对 Eden 区进行垃圾回收(Minor GC),将 Eden 区中不再被其它对象所引用的对象销毁,再加载新的对象放到 Eden 区。
- 然后将 Eden 区中剩余的对象移动到 S0 区。
- 如果再次触发垃圾回收,则将 Eden 区和 S0 区中幸存下来的对象放到 S1 区。S0 区和 S1 区依次交换。
- 如果再次经历垃圾回收,此时会重新放回S0区,接着再去S1区。
堆内存包含
新生代8:1:1(Eden+Survivor0+Survivor1)+老年代= 1:2
参数设置
执行GC的时机?
1、System.gc(); 不一定GC
2、Eden区,会发YGC
常见面试题
百度 三面:说一下 JVM 内存模型吧,有哪些区?分别是干什么的?
蚂蚁金服: Java8 的内存分代改进。JVM 内存分哪几个区,每个区的作用是什么?
一面:JVM 内存分布/内存结构?栈和堆的区别?堆的结构?为什么有两个 survivor 区?
二面:Eden 和 Survivor 的比例分配
小米: jvm 内存分区,为什么要有新生代和老年代
字节跳动 二面:Java 的内存分区
二面:讲讲 jvm 运行时数据区,什么时候对象会进入老年代?
京东 JVM 的内存结构,Eden 和 Survivor 比例。
JVM 内存为什么要分成新生代,老年代,持久代。新生代中为什么要分成 Eden 和 Survivor。
1)共享内存区划分
1.共享内存区 = 持久代 + 堆(注;jdk1.8及以上jvm废弃了持久代)
2.持久带代= 方法区 + 其他
- Java堆 = 老年代 + 新生代
- 新生代 = Eden(伊甸区) + S1(幸存1) + S2(幸存2)
2)为什么分年老代和新生代
- 新生代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个EdenSpace和2个SuvivorSpace(from 和to)。
- 老年代(Tenured Gen):年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。
3)为什么要分为Eden和Survivor?为什么要设置两个Survivor区?
-
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major
GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。 -
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full
GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。 -
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次MinorGC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor
GC,Eden和S0中的存活对象又会被复制送入第二块survivor space
S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。
天猫 一面:JVM 内存模型以及分区,需要详细到每个区放什么。
一面:JVM 的内存模型,Java8 做了什么修改
拼多多 JVM 内存分哪几个区,每个区的作用是什么?
美团 Java 内存分配 jvm 的永久代中会发生垃圾回收吗? 一面:jvm内存分区,为什么要有新生代和老年代?
1、请解释JVM整体结构,包括哪几部分及其功能。
2、请解释JVM 堆空间默认配置比例,通过哪些参数可以调整。
3、请简介JVM如何加载类,以及JVM实例化对象的步骤。
4、请简介常见的垃圾回收算法,并解释说明特点。