jvm启动时进行一系列的工作,其中一项就是开辟一块运行时内存。而这一块内存中又分为了五大区域,分别用于不同的功能。
程序计数器
记录程序运行的下一条指令的地址,这里的“地址”可以是一个本地指针,也可以是在方法字节码中相对于该方法起始指令的偏移量。如果该线程正在执行一个本地方法,那么此时程序计数器的值为”undefined”.在多线程环境下,每一个线程都有自己的程序计数器,在jvm调度线程时,会把当前的线程的程序计数器保存到快照,以便下次线程获取执行时间时获取
VM Stack
虚拟机栈是Java方法执行的内存模型,每个方法执行的时候,会在栈中创建一帧用于存储局部变量表、操作数栈、动态链接、方法出口。方法开始调用时,会创建栈帧并入栈,方法执行结束时会出栈。每个线程都有自己的栈。
动态链接:是一种在常量池中指向方法的符号引用,需要在运行期确定为直接引用
方法出口:当前执行方法的调用者的程序计数器,或异常处理表的地址
可以通过 -xxs 大小 来配置栈的大小,当嵌套调用使用不当,会导致方法不停的入栈,最终导致栈空间被占满产生 StackOverflowError
本地方法栈
Heap
堆是用于存放对象实例的地方,几乎所有对象实例在堆中分配。堆是线程共享的,这是多线程时同步机制的原因。
堆是GC管理的主要区域,GC在对堆进行回收前,首先要确定对象是否已死(不可能再被使用的对象)
判断对象是否存活的算法有两种:引用计数算法、可达性分析算法
引用计数算法是为每一个对象添加一个引用计数器,每当有一个引用指向它时,计数器就加一,任何时刻计数器为0的对象就不可能再被使用。这种算法实现简单,但是它很难解决对象循环引用的问题(何为循环引用见下方备注)
可达性分析算法是Java语言正在使用的算法。它的基本思想是通过一系统被称为“GC Root”的对象为起点,从这个起点向下搜索,搜索走过的路径称为引用链,当一个对象不再任何引用链上时,则说明这个对象是不可能再被使用的。
在Java语言中,GC Root包括以下几种对象:
- 虚拟机栈中引用的对象
- 本地方法栈中JNI引用的对象
- 方法区中类静态成员变量引用的对象
- 方法区中常量引用的对象
可以看出分析对象是否存活,都与引用有关。在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为 强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)
- 强引用
强引用即为原来意义上的引用,只要强引用存在,被引用的对象就不会被回收
- 软引用
SoftReference类表示软引用,对于被软引用关联的对象,在系统将要发生内存溢出时,会把这些对象列入回收范围后,进行二次回收
- 弱引用
WeakReference类表示弱引用,对于被弱引用关联的对象,只能生存到下一次垃圾回收发生之前
- 虚引用
PhantomReference类表示虚引用,虚引用不对关联的对象的生存时间构成影响,也无法取得对象实例,它唯一的作用是在对象被GC回收是收到一条系统通知
堆得大小可以通过-Xmx和-Xms来控制。对于主流的Jvm,GC基本都采用分代收集的算法。基于这个算法, Java堆又分为新生代(Young Generation)和老年代(Old Generation),新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例。老生代用于存放新生代中经过多次垃圾回收(也即Minor GC)仍然存活的对象。
永生代(Permanent Space)为方法区,到jdk1.8之后没有了永生代,取而代之的为元空间 (Meta Space)
方法区
方法区也为所以线程所共享,用于存放已加载的类信息、静态变量、常量和即时编译器编译后的代码。-XX:MaxPermSize用于设置方法区大小
直接内存
直接内存不是虚拟机运行时数据区的一部分。通过Native函数库直接分配的堆外内存,然后通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作
Stack 栈
栈也叫栈内存,主管java程序的运行,是在线程创建时创建,它的生命周期跟随线程的生命周期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈也随之结束。 基本类型的变量和对象的引用变量都是在函数的占内存中分配。
栈存储什么?
栈帧主要保存三类数据:
本地变量:输入参数(形参)和输出参数(返回值的参数)以及方法内的变量;
栈操作:记录出栈、入栈的操作;
栈帧数据:包括类文件,方法等。
Heap 堆
java堆中一共分为三大区域:
新生区(伊甸区,生存1区,生存2区),养老区和永久存储区。
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用最后被垃圾回收器手机,结束生命,新生区又分为两部分:伊甸区和生存区,所有的类都是在伊甸区new出来的,当伊甸区的空间用完时,程序还需继续创建对象,JVM的垃圾回收器将对伊甸区的进行垃圾回收(Minor GC),将伊甸区中的不在被其他对象锁引用的对象记性销毁,在对该区进行垃圾回收,然后将幸存的对象移到0区,若生存0区也满了再对该区进行垃圾回收,然后将存下的对象移到1区,如果1区也满了再移到养老区,若养老区也满了这个时候产生Major GC(Full GC),进行养老区的内存清理,若养老区执行了Full GC后无法进行对象的保存 则会产生 OutOfMemoryError异常,产生该异常的原因有(1) java虚拟机的堆内存设置不够,可通过参数-Xms 、-Xmx来调整 (2)代码创建了大量的大对象,并且长时间不能被垃圾回收器收集。
养老区用于保存从新生区筛选出来的java对象,一般池对象在这个区域活跃。
永久区是一个常住内存区域,用于存放JDK自身所携带的Class,Interface的元数据,被装在进此区域的数据是不会被垃圾回收器收掉的,关闭JVM才会释放此区域的内存。