java virtual machine :java程序的运环境(java二进制 字节码的运行环境)
好处:
一次编写,到处执行(跨平台)
内存管理,垃圾回收功能
数组下标检查
多态
jdk 包含 jre 包含 jvm
jvm运行时内存区域
程序计数器是当前线程正在执行的字节码文件的地址,程序计数器是线程隔离的,每一个线程在工作的时候都有一个独立的计数器.
特点:
线程私有的
不会存在内存溢出
程序执行时程序计数器是有值的,记录的是正在执行的 字节码文件地址
作用:
单线程的话代码就是按顺序执行的,单线程看起来是可有可无的,多线程下,如果一个线程因为时间片耗尽而挂起.蛋挞再次获取的时候,需要从挂起的地方继续执行,通过程序计数器俩记录程序的字节码执行位置,每个线程都有一个自己的程序计数器.
栈是运行时单位,堆是存储单位;栈解决程序的运行问题,即程序如何执行,或者说如何处理数据,堆解决的是数据存储的问题,即数据怎么放以及放在哪.在运行时内存模型中栈的运行速度仅次于程序技术器.栈不需要垃圾回收(GC)
线程私有
生命周期和线程相同
每次执行方法的时候,Java虚拟机栈就会会同步的创建一个栈帧,用于存储临时变量表,操作数栈,动态链接,方法出口等信息.方法的开始与执行,对应着栈帧的入栈到出栈的过程.
局部变量表:存放了java虚拟机的基本数据类型,和对象引用(一个指向对象的指针)和return address类型
对于栈内存的规定,如果线程请求的栈的深度大于虚拟机所允许的深度将抛出StackOverFolwError异常,HotSpot虚拟机的站容量是不支持动态扩容的,只要线程申请栈空间成功了,就不会抛出OutOfMemeoryError异常
执行引擎运行的所有字节码文件,只对当前栈帧进行操作,如果该方法用其他方法,新的栈帧会被创建出来,放在栈的顶端成为新的当前栈顶,返回(两种方式:一种是return一种是为catch的异常抛出返回)
局部变量表也被称之为局部变量数组或本地变量表 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference) ,以及 returnAddress类型。 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
局部变量表中的槽位是可以重复利用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的.
方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁
操作数栈,主要用于保存运算过程中的中间结果,同时作为计算过程中变量的临时储存空间
public class OperandStackTest { public void testAddOperation() { //byte、short、char、boolean最终还是以int类型存放 byte i = 15; int j = 8; int k = i + j; } } //反编译后 testAddOperation 方法的字节码指令 0: bipush 15 2: istore_1 3: bipush 8 5: istore_2 6: iload_1 7: iload_2 8: iadd 9: istore_3 10: return //结果分析 0 将15压入操作数栈 2 将15出栈放入局部变量表,它在局部变量表中的index是1(0是this), 3 将8压入操作数栈 5 将8出栈放入局部变量表,index是2 6 将局部变量表中的index为1的值入栈 7 将局部变量表中的index为2的值入栈 8 8出栈,15出栈,执行引擎执行加操作,把结果23压栈 9 23出栈存入局部变量表 10 返回
动态链接是在编译期间无法确定的方法调用的一种链接方式。当Java程序执行到一个方法调用指令时,虚拟机会进行动态链接,即根据方法调用指令中的符号引用在运行时期解析出调用的目标方法,并将符号引用替换成实际引用。这个过程需要通过符号引用中的类、方法名以及方法描述符来唯一确定目标方法。
是虚拟机管理的最大的一块区域,被所有线程共享的一块内存区域在虚拟机启动时进行创建,唯一目的是存放对象实例,是垃圾收集器管理的内存区域,堆里还可以划分线程私有的缓冲区, 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除(因为垃圾收集的时候才会去扫描垃圾)。堆是GC ( Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。
现代垃圾收集器大部分基于分代收集理论设计,堆内存细分类:
java7 及其以前分为:
新生区
养老区
永久区
java8值周堆内存逻辑上分为三部分:
新生区
养老区
元空间
Java堆区用于存储Java对象实例,堆的大小在JVM启动时就已经设定好了,可以通过选项"-Xmx"和"-Xms"来进行设置。
-Xms用于表示堆区的起始内存,等价于-XX:InitialHeapSize
-Xmx则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
一旦堆区中的内存大小超过“-Xmx"所指定的最大内存时,将会抛出OutOfMemoryError异常。
通常会将 -Xms 和 -Xmx 两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。
存储在VM中的Java对象可以被划分为两类:一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速;另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一
Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(OldGen)。其中年轻代又可以划分为 Eden 空间、Survivor0 空间和 Survivor1空间(有时也叫做from区、to区)。
默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3。
在HotSpot中,Eden空间和另外两个survivor空间缺省所占的比例是8:1:1,开发人员可以通过选项 -XX:SurvivorRatio 调整这个空间比例。比如-XX:SurvivorRatio=8。
几乎所有的Java对象都是在Eden区被new出来的。绝大部分的Java对象的销毁都在新生代进行了。IBM公司的专门研究表明,新生代中80% 的对象都是“朝生夕死”的。
可以使用选项"-Xmn"设置新生代最大内存大小。这个参数一般使用默认值就可以了
下面我们来看看对象分配的过程:
new的对象先放伊甸园区。此区有大小限制,当伊甸园的空间填满时,程序又需要创建对象,此时JVM的垃圾回收器将对伊甸园区进行对象进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁,然后将伊甸园中的剩余对象移动到幸存者0区。幸存者区中会有年龄计数器,每活过一次GC,年龄就会+1。
如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。当幸存者区中的对象超过15岁时,就会被分配到老年区。进入老年代的年龄是可以设置的,设置参数:
设置参数:
-XX:MaxTenuringThreshold=
在养老区,相对悠闲。当养老区内存不足时,再次触发GC(Major GC),进行养老区的内 存清理。
若养老区执行了Major GC之后发现依然无法进行对象的保存,就会产生OOM异常。
这里要注意的是,当new出特别大的对象(Eden放不下)时,首先要进行一次YGC,还是放不下,就直接放到老年代了;如果Servivor区满了,而Eden区没满,此时不会触发YGC。多余的对象会直接放到老年代。
GC频繁在新生区收集,很少在养老区收集,几乎不在永久区/元空间收集。