1、堆的核心概述
一个JVM实例只存在一个堆内存,是Java内存管理的核心区域,在JVM启动时创建,空间大小也确定了,是JVM管理的最大一块内存空间。
1、堆的核心概述
一个JVM实例只存在一个堆内存,是Java内存管理的核心区域,在JVM启动时创建,空间大小也确定了,是JVM管理的最大一块内存空间。
《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但逻辑上应该视为连续的。所有的线程共享,还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。所有的对象实列以及数组都应当在运行时分配在堆上,从实际使用角度看,应该是几乎。
数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。
方法结束后,堆中的对象在垃圾收集的时候才会被移除,是GC(Garbage Collection)执行垃圾回收的重点区域,大都使用分代垃圾收集算法。
2、新生代与老年代
存储在JVM中的Java对象可以划分为两类
生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速。 生命周期非常长,某些情况能够与JVM生命周期保持一致。
堆区进一步细分,可分为新生代(YoungGen)和老年代(OldGen),其中新生代可以细分为Eden空间,Survivor0,Survivor1空间(也叫from区和to区)。 配置新时代与老年代在对结构的占比(开发中一般不调,除非确定生命周期长的对象很多。)
默认-XX:NewRatio=2,表示新时代占1,老年代占2,新生代占整个堆的1/3 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5。
HotSpot中,Eden和Survivor空间占比是8:1:1,开发人员可以通过 -XX:SurvivorRatio=N 调整空间比例,使用-XX:-UserAdaptiveSizePolicy(关自适应的内存分配策略)调整空间比例无效。
Java对象几乎都是在Eden区创建的,80%左右的对象在新生代进行销毁,可以使用 -Xmn 设置新生代最大内存大小,一般不设置,使用默认值。
3、栈的核心概述
不同平台CPU架构不同,由于跨平台性的设计,Java的指令都是根据栈来设计的。
优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
栈(stack):运行时单位,解决程序运行问题,即程序如何执行,如何处理数据。
堆(heap):存储单位,解决数据存储问题,即数据怎么放,放哪儿。
4、Java虚拟机栈是什么?
Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。线程在创建时会创建一个虚拟机栈,内部保存栈帧(Stack Frame),对应Java的方法调用,是线程私有的,生命周期和线程一致。
主管程序运行,保存方法局部变量(8种基本数据类型及引用对象的引用地址),部分结果,并参与方法的调用和返回。是一种快速有效的分配存储方式,访问速度仅次于程序计数器。会有OOM(内存溢出)问题,但不存在垃圾回收问题。
Java栈的大小可以是动态或者固定不变的。采用固定大小,每个线程的Java虚拟机栈容量可以在线程创建时独立选定。如果请求分配的容量超过Java虚拟机栈允许的最大容量,会抛出StackOverflowError异常。
采用动态扩展,并在尝试扩展时无法申请到足够的内存,或者在创建新线程时没有足够内存去创建对应的虚拟机栈,将抛出OutOfMemloryError 异常。
5、栈(Stack)的存储单位
栈中的数据以栈帧(Stack Frame)的格式存储,正在执行的方法都有各自对应的栈帧,栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。
JVM直接对Java栈的操作只有:入栈(压栈、进栈)和出栈(弹栈),遵循“先进后出”/“后进先出”原则。
在一个活动的线程中,同一时间只有一个活动的栈帧,即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧,与当前栈帧对应的方法就是当前方法,定义这个方法的类就是当前类。
执行引擎运行的字节码指令只针对当前栈帧进行操作,如果在当前方法中调用了其它方法,对应的新栈会被创建出来,并放在栈的顶端,成为新的栈帧。
不同线程中所包含的栈帧不允许存在互相引用,即不能在一个栈帧之中引用另一个线程的栈帧。
Java方法返回函数的两种方式:使用return指令和抛出异常,都会导致栈帧被弹出。
6、局部变量表(Local Variables Table)
局部变量表也称为局部变量数组或本地变量表,主要用于存储方法参数和定义在方法体内部的局部变量,这些数据包括基本数据类型、对象引用(reference),以及returnAddress类型,表建立在线程的栈上,是线程的私有数据,不存在数据安全问题。
局部变量表所需的容量大小在编译期确定下来,并保存在方法的Code属性的maximun local Variables数据项中,方法运行期间不会改变局部变量表的大小。
方法嵌套(如:递归)调用的次数由栈的大小决定。栈越大,方法嵌套调用次数越多。函数的参数和局部变量越多,局部变量表膨胀,栈帧就越大。
局部变量表中的变量只在当前方法中有效。方法执行时,虚拟机通过局部变量表完成参数数值到参数变量列表的传递过程。方法调用结束后,随方法栈帧的销毁而销毁。
局部变量表不存在系统初始化过程,因此定义了局部变量必须人为初始化,否则无法使用。