目录
(二)本地方法栈(Native Method Stacks)
一. JVM内存结构
JVM的内存结构说的就是运行时数据区,该区域又分成五个内存结构:
- 线程私有:Java栈、本地方法栈、程序计数器
- 线程共享:堆、方法区
下面开始对每个结构进行讲解。
(一)Java栈(Java Stack)
- 该区域是JVM内存结构中最重要的区域,它是Java线程执行方法的内存模型,每个线程都会拥有自己独立的栈区,不与其他线程共享;
- 线程中的每个方法在执行时都会创建一个属于该方法的栈帧(Frames),用于存储局部变量表(Local Variables)、操作数栈(Operand Stacks)、动态链接(Dynamic Linking)、方法出口信息等;
- 对于八种基本数据类型(byte、short、int、long、float、double、char、boolean)的局部变量,在栈中存储的是它们对应的值;
- 对于定义定义在方法体中的引用类型的变量存储的是对象引用,不是对象本身,对象引用是对象在堆中的地址;
- Java栈不需要进行垃圾回收,因为它的生命周期是跟线程一致的,只要线程执行完,就会被释放掉。
JVM为该区域定义了下面两种异常类型:
- 线程请求的栈深度大于虚拟机栈所允许的深度,将抛出StackOverFlowError异常
- 若虚拟机栈可动态扩展,当无法申请到足够内存空间时将抛出OutOfMemoryError
- 通过jvm参数–Xss指定栈空间,空间大小决定函数调用的深度
(二)本地方法栈(Native Method Stacks)
本地方法栈也是一个栈结构,并且是线程私有,作用是登记本地方法,即native方法,JVM执行引擎会在执行时加载本地方法库。
(三)程序计数器(The PC Register)
程序计数器是一个线程私有的指针,它指向了方法区中的方法字节码,记录线程即将要执行的程序指令位置,交由执行引擎读取下一条指令然后执行。
(四)Java堆(Java Heap)
- Java堆是线程共享的内存区域。它会在虚拟机在启动时创建。
- Java堆唯一目的就是存放对象实例,所有的对象实例、实例变量(不包括static修饰的成员变量)及数组都要在Java堆中分配内存空间,当空间耗尽无法继续分配内存时会抛出OutOfMemoryError异。
- 该区域也是垃圾收集器管理的主要区域;
- 可以通过设置-Xmx、–Xms参数指定最大堆、最小堆的值。
堆结构详解
堆大小=新生代+老年代。
1、新生代(Young Generation)
默认占堆大小的1/3,是类诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾收集器收集消亡。
新生代分成两个部分:
- 伊甸区(Eden Space)
所有的类都是在伊甸区被new操作创建。
- 幸存者区(Survivor Space)
幸存者区也分成了两个区域,0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸区空间耗完而程序又需要创建对象时,JVM 的垃圾收集器将对伊甸区进行垃圾回收(Minor GC),对其中所有已经失去引用的对象实例进行销毁,然后将剩余的对象移动到幸存者0区,如果之后幸存者0区也满了,再对0区进行垃圾回收,然后将剩余对象移动到1区。
- Survivor中的From与To区
刚才已经说明了Survivor区分为0区和1区,除此之外还有另一个概念,From Space与To Space。这两个区是相对的,在JVM没有触发Minor GC前,0区对应From,1区对应To,但是每触发一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survivor-from区会把一些仍然存活的对象复制进Survivor-to区,然后清理空间,并且将此时在Survivor空间中存活的对象的年龄设置为1,以后Survivor区的这些对象每熬过一次GC年龄就加1,当对象年龄达到某个值时(默认是15),它们就会移到老年代中。
2、老年代(Old Generation)
该区域占堆大小的2/3,JVM会将新生代中经过多次GC后还存活的对象移动到老年代区,如果老年区满了,将通过Major GC(Full GC)对老年代和新生代中不再被使用的对象资源进行回收;如果在老年代区进行Full GC后发现依然无法申请内存空间来创建对象,这时就会产生OOM异常(OutOfMemoryError)。
需要注意的是,GC在运行的过程中都会发生STW(Stop The World)现象,暂停JVM中运行的所有业务线程,集中系统资源更高效地进行垃圾回收工作;但是不是所有GC收集器都是会暂停业务线程进行GC工作,CMS收集器和G1收集器是可以做到GC线程和业务线程并发执行的,这块知识点以后会在另开一篇垃圾回收机制的文章中介绍。
(五)方法区(Method Area)
方法区跟Java对一样是各个线程共享的内存区域,它存储已被Java虚拟机加载的类信息、常量、静态变量、静态方法和普通方法字节码信息、以及一些即时编译器编译后的代码等;当我们通过new关键字创建一个类的实例对象时需要的类定义信息就是从这里获取,方法区还有另一个别名叫做非堆(Non Heap)。
1、元空间(Meta Space)
从jdk1.8开始元空间取代了之前的java堆的永久代(Permanent Generation),本质上元空间和永久代是类似的,都是对JVM规范中方法区的实现。
两者的区别在于元空间的内存大小并不是JVM分配,而是直接使用本地物理内存;而永久代是在JVM中,永久代在逻辑结构上是属于堆,但是物理结构上不属于堆;元空间也有可能发生OutOfMemory异常。
- Jdk1.6及之前: 有永久代,常量池在方法区
- Jdk1.7: 有永久代,但已经逐步“去永久代”,常量池在堆
- Jdk1.8及之后: 无永久代,常量池在元空间
元空间的动态扩展,默认–XX:MetaspaceSize值为21MB的高水位线。一旦触及这个大小限制,则Full GC将被触发并卸载没有用的类(类对应的类加载器不再存活),然后高水位线将会重置。新的高水位线的值取决于GC后释放的元空间。如果释放的空间少,这个高水位线则上升。如果释放空间过多,则高水位线下降。
参考资料: