一.程序计数器
1.是当前线程所执行的字节码的行号指示器,字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理都需要依赖程序计数器来完成
2.多线程情况下,需要依赖程序计数器来记录当前线程执行的位置,从而当线程切换回来的时候知道上次线程执行到哪了
随着线程的结束而死亡
二.java虚拟机栈
描述的是java方法的执行内存模型
(实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。
也是线程私有的,也是随着线程的死亡而死亡
三.本地方法栈
和java虚拟机栈作用很相似,区别是java虚拟机栈执行的是java方法(也就是字节码)服务,而本地方法栈则为虚拟机用到的Native方法服务,
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
四.堆
存放对象实例的,几乎所有的对象实例以及数组都在这里分配
是线程所共享的
五.方法区
方法区也是线程共享的内存区域
用于存储已被虚拟机加载的类信息,常量,静态变量即使编译器编码后的代码等数据
java虚拟机把它当做的堆的逻辑部分,但是但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
六、运行时常量池
JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
java堆:由于现在的垃圾回收很多采用分代收集,所以java堆还可以细分为新生代和老年代
再细致还有Eden ,From Survivor, To Survivor等区域
JVM内存分配策略
针对对象首先在Eden区分配,当Eden区装不下的时候就进行垃圾回收,Eden区一共是8M(From Survivor, To Survivor都是一M),要放3个2M,一个4M的对象,3个2M的已经放进去了,4M的要放的时候Eden放不下,就进行一次垃圾回收,发现From Survivor, To Survivor区域是1M放不进去,于是就放进老年区,4M的放进Eden
大对象(典型的大对象就是很长的那种数组,字符串)直接进入老年代,通过-XX:PretenureSizeThreshold参数设置大小,超过这个大小的就进入老年代
长期存活的对象进入老年代,什么叫长期存在的呢。进行一次垃圾回收从Eden到Survior区,并且年龄加1,每在survior中熬过一次垃圾回收年龄就加1,年龄到达15(默认),就放进老年代。
在javaGuide上看到的:
对象首先被放进eden区域,当一次垃圾回收会把eden和from中的不需要回收的年龄加1后放进to中(到15岁就会放进老年代),然后对这两个区域进行回收,回收算法因为回收的东西较多就用复制算法,收完之后就把to和from转变角色,to变为from,from变为to,不管怎样会保证to是空的,当下一次垃圾回收to区域满的时候就把所有对象都放进tentired区域,对tentired区域回收就使用标记清除或者标记整理来处理
(上图所示的 eden 区、s0("From") 区、s1("To") 区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1("To"),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
来设置。经过这次GC后,Eden区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到老年代中)