内存模型图解概览
对于图上参数的控制
- -Xms设置堆的最小空间大小
- -Xmx设置堆的最大空间大小
- -XX:NewSize设置新生代的最小空间大小
- -XX:MaxNewSize设置新生代的最大空间大小
- -XX:PermSize设置永久代的最小空间大小
- -XX:MaxPermSize设置永久代的最大空间大小
没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。
老年代空间大小=堆空间大小-年轻代大空间大小复制代码
jvm和系统调用之间的关系
各个区域的作用和详解
java堆(Heap)
图解
- 堆内存是java虚拟机所管理的内存中最大的一块,
- 被所有线程共享的内存区域,
- 在虚拟机启动的时候创建,
- 存放的是对象的实例,几乎所有的对象实例都在这里分配内存
- 是垃圾收集器管理的主要区域,也被称之为“GC堆”
- 堆分为新生代和旧生代,各自所占的比例为新生代为1/3、旧生代2/3,新生代存储的是刚创建的对象、比较小的对象。旧生代存储的为较大的对象、存活时间较长的对象
- 新生代可以细分为:Eden空间、From Survivor空间、To Survivor空间,各自所占的比例为8:1:1
- 根据java虚拟机规定,java堆可以处在物理上不连续的内存空间,只要是逻辑上是连续的即可
- 堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError(OOM)异常。
将一个类进行初始化的过程示例,对象A创建后,在堆内存的分配和流转
- 当对象A被new出来的时候,存在放Eden区
- 当第一次放生GC后,Eden区存活下来的对象A会被复制到To Survivor区,同时From Survivor区的对象也会被复制到To Survivor区
- GC会清空Eden区和From Survivor区中的所有存储对象,因为之前已经将对象复制了,所以可以完全清除
- To Survivor和From Survivor会交换角色,要保证在GC发生之前,To Survivor永远是空的那个
- 下次GC发生时,重复上述步骤。将Eden中存活的对象复制到To Survivor,将From Survivor中活的对象也复制到To Survivor。
- 发生GC时,From Survivor中存活的对象并不是全部都会被复制到To Survivor中,而是根据这个对象在Survivor区中存活了多久而决定去向,当一个对象在Survivor中存活了很久(即经历了多次GC还没死),就会在发生GC时被复制到旧生代中。
堆内存分配策略明确以下三点:
-
对象优先在Eden分配。
-
大对象直接进入老年代。
-
长期存活的对象将进入老年代。
为什么对象在新生代中复制来复制去的,而不是将死的直接清除,老的直接复制到旧生代 这样做的好处就是减少了内存碎片,而直接清除的话会使内存很零碎。详情可以了解一下java垃圾回收算法中的复制算法。
方法区 (Method Area)
有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来
存储的内容
- 各个线程共享的内存区域
- 存储已经被虚拟机加载的类信息,常量,静态变量,既时编译器编译后的代码等数据
- 有人也称其为永久代,但是两者并不等价
- 不需连续的内存,可以选择固定大小,可以选择扩展,可以选择不实现垃圾收集
- 并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。
- 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
- 方法区有时被称为持久代(PermGen)。
程序计数器(Program Counter Register)
- 程序计数器是一块较小的内存空间,可以看做当前线程所执行的字节码行号指示器
- 可以称之为PC寄存器
- 字节码解释器在工作的时候,通过改变程序计数器的值,来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖于这个计数器完成
- 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
- 每条线程都有以及一个独立的线程计数器
- java方法:这个计数器记录的是正在执行的虚拟机字节码指令的地址
- Native方法:计数器值为空(Undefined)
- PC寄存器是对物理PC寄存器的一种抽象模拟,线程私有,生命周期与线程的生命周期保持一致
程序计数器为什么是线程私有
所谓的多线程在一个特定的时间段内只会执行其中的某一个线程方法,CPU会不停的切换任务,为了能够准确的记录各个线程正在执行的当前字节码地址,最好的办法是每一个线程都分配一个PC寄存器,这样一来每一个线程都能独立运算,不会出现相互干扰
JAVA 栈(jvm stacks)
- java栈属于线程私有,他的生命周期和线程相同
- jvm stacks描述的是java方法执行的内存模型:每一个方法执行的时候,都会同时创建一个栈帧,用于存储局部变量表,操作栈,动态链接,方法出口等信息
- 每一个方法从被调用直至执行完成的过程,就是对应的一个栈帧在虚拟机中入栈到出栈的过程
本地方法栈(Native Method Stacks)
- 本地方法栈和虚拟机栈发挥的功能非常相似,区别就是虚拟机栈为虚拟机执行java方法服务,本地方法栈则是为虚拟机使用到的Native方法服务
- 本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。