JVM基础回顾记录(一):JVM的内存模型

1、JAVA程序执行流程

JAVA程序执行的基本流程(基于HotSpot):java

图1算法

2、内存模块划分

2.1:程序计数器

程序计数器是一块较小的内存空间,是当前线程执行字节码的行号指示器,字节码解释器就是经过改变这个计数器的值来获取下一条须要执行的字节码指令,其中分支、循环、跳转和异常处理,线程恢复等基础功能均须要依赖该计数器完成。因为jvm的多线程是经过线程轮流切换并分配CPU执行时间的方式实现,在任什么时候刻,一个CPU都只会执行其中一条线程里的指令,为了使线程发生切换后能够顺利的定位到上次发生切换时的执行位置,每一个线程都有一个独立的程序计数器,每条线程的计数器相互独立,这块存储区域被称为线程私有内存。多线程

2.2:方法区

也是线程共享的一块区域,这块区域主要用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,HotSpot虚拟机实现该区域时采用堆的一块区域实现,目的是为了让GC分代收集扩展至方法区(好比类卸载时是须要GC参与的),所以在堆空间里划分出一个代来存储方法区里的内容,这块区域一般被称做“永久代”,该块区域大小经过-XX:MaxPermSize来设置(所以可能会出现OOM的状况),不过须要说明的是,J8已经没有这块区域了,并且J7的时候已经将常量池由该块区域转移到实际的堆内存里了(实验证实,J7和J8的时候,常量池已经被存进了实际的堆内存,但区别是J7的类信息等还放在永久代中)。J8有了元数据存储,已经完全去除了永久代的概念,具体了解:Java8内存划分jvm

2.3:堆

jvm管理的最大一块区域,jvm启动时建立,用来存放对象实例,几乎全部的对象都在这里分配内存,经过-Xmx-Xms控制其大小。这块区域是GC管理的主要区域,从内存回收角度来看,如今的内存回收基本都采用分代收集算法,因此该区域还能够细分(如图1堆空间细分),进一步划分的目的是为了更好的回收内存或更快的分配内存。这里列一下例子里用到的参数(也是比较经常使用的参数):xss

名称 含义
-xms 堆初始化大小
-xmx 堆最大大小(通常来讲,-xms和-xmx设置大小一致,以免每次垃圾回收完成后JVM从新分配内存)
-xmn 新生代大小(结合图1理解),这里须要提一下-XX:newSize、-XX:MaxnewSize这俩参数,第一个是指新生代初始大小,第二个是指新生代最大大小,一样为了不JVM从新分配内存,这俩数值通常设置为同样的,因此jdk4出来了-xmn,一个配置,能够对上述俩参数同时生效
-XX:NewRatio 新生代(Eden+2Survivors)与老年代的大小比值,若是值为2,则新生代:老年代=1:2(若是设置了-xmn指定了新生代大小,则无需设置此项,两个都设置,只生效一个)
-XX:SurvivorRatio 新生代中,Eden区与两个Survivor的大小比值,若是设置为4,则eden:survivor1:survivor2 = 4:1:1)
-xss 栈大小,通常默认128k,若是调用栈不是很深(好比很深的递归程序),保持默认便可

 表1spa

2.3.1:堆配置详解

如今让咱们把图1中的“堆空间细分”部分放大,结合下面的配置信息和表1中的含义(-Xss和永久代不会体如今图里),来用图说明下:操作系统

配置1:-Xmx3072m  -Xms3072m  -Xmn2g -XX:SurvivorRatio=4  -Xss128k.net

上面的配置表示堆区总大小为3550M,新生代大小为2G(2048M,这里只是说明问题才配置这么大,实际生产中,新生代要小于老年代),新生代Eden区和两个Survivor区的比例为4:1:1,用图来直观的表达一下这个配置:线程

图2指针

配置2:-Xmx3072m  -Xms3072m  -XX:NewRatio=2 -XX:SurvivorRatio=4  -Xss128k

上面的配置表示堆区总大小为3550m,新生代:老年代=1:2(HotSpot默认),新生代eden区和两个survivor区的比例为4:1:1,用图来直观的表达一下这个配置:

 

图3

 

2.4:虚拟机栈

也就是常说的栈,线程私有,生命周期与线程相同,虚拟机栈用来描述java方法(java method)执行的内存模型,每一个方法执行时都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接、方法出口等信息,一个方法的调用到其完成调用对应一个栈帧在虚拟机的入栈到出栈。

局部变量表里存放着编译期可知的基本数据类型、对象引用Reference(多是指向实例对象地址的一个引用指针,也多是表明实例对象的一个句柄)、以及returnAddress(指向一条字节码的执行命令的地址)。在此区域,若是线程的请求深度大于虚拟机容许的深度,将会抛出StackOverFlowError异常,这个能够经过一个无限制递归或者递归深度设置一个很大的数来证实:

public class Recursion {

    public static int i = 0;

    public static void main(String[] args) {
        Recursion t = new Recursion();
        t.recursion();
        System.out.println("程序正常结束~");
    }

    public void recursion(){
        i++;
        if(i > 30000){ //这里作适当调整,减少则不报栈溢出异常,扩大(好比这里的3w)就会报栈溢出
            return;
        }
        recursion();
    }
}

看注释那里调整便可证实。栈大小经过-Xss参数来调整,须要注意的是,这个参数是对每一个线程生效的(栈帧),通常状况下,这个设置的值越小,支持建立的线程数就越多(并不是能够无限多,最终受操做系统限制,操做系统针对每一个进程也有个最大线程数的限制),栈里存储的大部分数据,都会随着栈帧的结束(即method调用完成)而被回收,因此通常递归更容易形成栈溢出问题。

2.5:本地方法栈

意义相似虚拟机栈,只不过本地方法栈服务于本地native方法调用(JNI),咱们经常使用的虚拟机HotSpot已将该区和虚拟机栈作了合并。

相关文章
相关标签/搜索