JVM内存模型划分
JVM划分为五个部分:
- 堆(heap):被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例,可以通过-Xmx 和-Xms控制大小
- 栈:每个方法在执行的时候都会创建一个栈帧,存储局部变量、操作数栈、动态链接、方法出口等
- 方法区:存放类信息、静态变量、常量等
- 本地方法栈:调用本地的Native方法时需要用到
- 程序计数器:记录正在执行方法的具体位置(虚拟机字节码指令的地址),如果是Natice方法,则为空
堆、方法区都是所有线程共享的,其他的则都为线程私有
如下图所示
堆
- 年轻代
年轻代占整个堆内存的1/3,年轻代内部又划分Eden、Survivor0、Survivor1三块区域,默认内存占比为8:1:1
为啥要划分成三块呢?
对象创建时会在Eden区存放,如果Eden区满了就会触发Young GC,GC标记完成之后,会把Eden区存活的对象全部挪到Survivor0区,新来的对象又会存放到Eden区,那么这个时候Eden和Survivor0就都存放了对象,那么在下次GC的时候,Eden区和Survivor0区的存活对象要放到哪里呢?这个时候就出现了Survivor1的区域,Survivor1就是用来存放在Eden和Survivor0中的存活对象。
注意:每次Young GC存活的对象年龄都会+1,默认加到15岁就会移动到老年代 - 老年代
老年代占整个堆内存的2/3,老年代顾名思义就是存放长期存活的对象以及存放Eden区放不下的大对象,老年代如果放满了就会触发 Full GC,Full GC除了会回收老年代 还会回收年轻代,方法区的垃圾,Full GC的速度一般会比Minor GC的慢10倍以上
注意:不管事Young GC还是Full GC在执行的时候都会STW。
栈
每个方法的执行都会在栈内创建一个栈帧
- 局部变量:方法内部的局部变量
- 操作数栈:执行方法的时候它会把每个操作都拿出来然后给进行对应的操作之后把结果返回给局部变量
- 动态链接:因为类加载的时候使用了懒加载,所以成员变量加载的时候不会去处理,需要在用的时候才会将符号引用转为直接引用
- 方法出口:记录当前方法执行完毕之后要执行的代码位置
方法区
与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息、静态变量、常量以及编译器编译后的代码等。
在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。
在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,
对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
本地方法栈
本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native)服务的
程序计数器
每条线程都有一个独立的的程序计数器,各线程间的计数器互不影响,因此该区域是线程私有的。
以上就是我对JVM内存模型的理解,欢迎指出存在问题。