最近几天一直在看相关书籍,想多了解一下java的底层实现,也为以后面试打点基础。下面将记录学习的到的知识点,巩固一下防止看过就忘记。
JVM运行时区域可以分为5个部分,第一个部分线程共享:方法区、JAVA堆;第二部分是线程私有:虚拟机栈、本地方法栈、程序计数器;如图1-1所显示
1.程序技术器(Program Counter Register)
程序计数器是一块很小的内存空间,它可以被看作是当前线程所要执行的字节码的行号指示器。在JAVA虚拟机的概念模型里,字节码解释器工作就是通过改变这个计数器的值来选取下一条需要执行的指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器。
当线程执行的是本地(由Native修饰的)方法时,这个计数器对应的值为空。
程序计数器是在此内存区域中唯一一个不会出现OutOfMemoryError异常的区域。
2.Java虚拟机栈(Java Virtual Mechine Stack)
Java虚拟机栈也是线程私有的,生命周期和线程同步,Java虚拟机栈描述的是Java方法执行的线程模型:每个方法被执行的时候,Java虚拟机栈同步也会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,也对应着一个栈帧在Java虚拟机中从入栈到出栈的过程。
栈帧由三部分组成:局部变量区、操作数栈、帧数据区。
具体的栈帧介绍可以参考:https://blog.csdn.net/u012988901/article/details/100043857
局部变量区被组织为以一个字长为单位、从0开始计数的数组,和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的,可以看作为临时数据的存储区域。除了局部变量区和操作数栈外,java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在java栈帧的帧数据区中。
局部变量表: 存放了编译器可知的各种基本数据类型、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。
3.本地方法栈(Native Method Stacks)
本地方法栈和Java虚拟机栈类似,区别是Java虚拟机栈是对虚拟机执行java方法服务,而本地方法栈是对本地(由Native修饰的)方法服务
4.Java堆(Java Heap)
Java堆是虚拟机管理最大的一块内存区域,也被个线程共享。该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置(最大最小值都要小于1G),前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小堆内存的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,当然为了避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。堆内存 = 新生代+老年代+永久代。在我们垃圾回收的时候,我们往往将堆内存分成新生代和老年代(大小比例1:2),如下图所示,新生代中由Eden和Survivor0,Survivor1组成,三者的比例是8:1:1,新生代的回收机制采用复制算法,在Minor GC的时候,我们都留一个存活区用来存放存活的对象,真正进行的区域是Eden+其中一个存活区,当我们的对象时长超过一定年龄时(默认15,可以通过参数设置),将会把对象放入老年代,当然大的对象会直接进入老年代。老年代采用的回收算法是标记整理算法。
image.png
5.方法区(Method Area)
方法区也是个线程共享的内存区域,用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。方法区又被称为"永久代"。
网友评论