内存结构图
下面一一介绍下:
程序计数器
程序计数器用于记录cpu下一条需要执行的指令的地址,帮助执行引擎完成对数据的加载,运算等工作。
程序计数器是线程私有的,生命周期与线程的生命周期保持一致。
为什么被设定为私有的呢?
为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个程序计数器,这样一来各个线程之间便可以进行独立计算,从而避免cpu频繁的进行上下文切换。
栈
每个线程在创建的时候都会创建一个「虚拟机栈」,每次方法调用都会创建一个「栈帧」。调用方法时栈帧入栈,调用结束方法出栈、
每个「栈帧」会包含几块内容:参数,局部变量、方法的返回地址等
栈是线程私有的,所以栈的生命周期和线程一致,当线程结束了,该虚拟机栈也就销毁了。
和堆对比
栈是线程私有,堆是线程共享
栈是运行
时的单位,而堆是存储
的单位
栈解决程序的运行问题
,即程序如何执行,如何处理数据。
堆解决的是数据存储的问题
,即数据怎么放,放哪里
栈溢出
栈不存在垃圾回收问题,因为出栈即相当于回收,但是存在栈溢出的情况,比如递归爆栈
本地方法栈
本地方法栈跟虚拟机栈的功能类似:
虚拟机栈用于管理 Java 函数的调用
而本地方法栈则用于管理本地方法的调用。这里的「本地方法」指的是「非Java方法」,一般本地方法是使用C语言实现的。
堆
1、堆内存细分
堆空间分为:新生区+养老区+元空间
新生区又被划分为Eden区、Survivor1区和Survivor2区
默认大小占比如下:
新生代:老年代 - > 1 : 2
Eden:From:to -> 8:1:1
所有的Java对象都是在Eden区产生的
并且绝大部分的Java对象在新生代就销毁
了,不会进入老年代
2 、堆是线程共享的
但是,堆空间也并非全部都是共享的,因为在堆中还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)
3、《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上
,注意这里是应当不是一定
所以实际是:大部分对象分配在堆上
。一小部分没有发生栈上逃逸的会在栈上分配
这点和c++不一样,c++是用new创建的对象放堆上,不用new的放栈上
4、默认生命周期为15,超过15进入老年代
为什么是15呢?
因为标识分代年龄的空间大小是4bit,最多记录到15.
5、堆内存大小
“-Xms"用于表示堆区的起始内存
“-Xmx"则用于表示堆区的最大内存
通常会将起始内存和最大内存配置成相同的值
,一开始就把最大的给你,别老是找我要了
一旦堆区中的内存大小超过“-xmx"
所指定的最大内存
时,将会抛出OOM
异常。
6、垃圾回收的频率
频繁在新生区收集,很少在老年代收集,几乎不再永久代|元空间
进行收集
方法区
首先明确:只有Hotspot才有方法区的概念。其他虚拟机是不存在这一概念的。
方法区是规范,是逻辑概念,原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一。
所以永久代和元空间都是HotSpot实现方法区这一概念的方式。
- Java8之前的版本,用
永久代
实现方法区; - Java8之后的版本,用
元空间
实现方法区;
元空间与永久代最大的区别在于:
永久代在堆内存中,元空间在堆外
JVM在1.8时用元空间代替永久代的原因
随着Web领域的发展,Java程序变得越来越大,需要加载的内容也越来越多,永久代是放在堆内存中的,占用的是堆内存空间,所以容易OOM
而元空间放在本地内存中,使用元空间之后,就可以将数据直接存储在本地内存当中,减少了OOM的可能性。