【JVM】内存小结
学习jvm内存的各个区域,总结这些区域的作用、服务对象、问题,是翻越JVM内存管理这堵围墙的第一步。
---《深入理解Java虚拟机》
一、JVM内存运行时数据区简介
java虚拟机在执行java程序的时候会把它所管理的内存划分成若干个不同的数据区域,这些区域有着各自的用途,以及创建和销毁的时间,而这些不同的区域则为如下的几个运行时数据区,如图:
如上所示,是Java虚拟机中“运行时数据区”的5个部分,我按照他们是否“线程共享”做了一个分类:
其中:
1.“线程共享”:堆区、方法区 (important)
2.“线程非共享”:程序计数器、虚拟机栈、本地方法栈。
所谓,线程是否共享,也即区域的创建和销毁的时间,是随着虚拟机进程的启动而存在,还是依赖用户线程的启动和结束而建立和销毁。可以参考下面这幅图:
这张图就相对于上述第一张图立体多了,“程序计数器”、“虚拟机栈”、“本地方法栈”都是每个“Thread”线程独一份的,至此,“运行时数据区”的结构以及“线程共享”的概念就了解了。
下面依据其特点,分别介绍这5个区域。
Addition:Oracle公司JavaSE规范中描述的java虚拟机规范,参考:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html
二、运行时数据区详解
-------------------------------------------------------------
前置条件:
1.java方法与Native方法
2.栈桢(Stack Frame)
-------------------------------------------------------------
1.程序计数器
1)是什么?
Program Counter Register是线程私有的一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
2)作用?
由于jvm中的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,in any time,一个CPU只会执行一条线程中的指令。于是,为了线程切换后能够恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间技术互不影响,线程私有,这就是线程计数器存在的意义。
Addition:此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2.Java虚拟机栈
1)是什么?
Java Virutal Machine Stacks是线程私有的,生命周期与线程相同。主要用于存储局部变量表、操作数栈、动态链接、方法出口等信息的内存空间。
2)做什么?
虚拟机栈描述的是Java方法执行的内存模型: 每个方法在执行时,会创建一个栈桢用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至完成的过程,就对应着一个栈桢在虚拟机栈中入栈到出栈的过程。
这里较为常见的是“局部变量表”,java程序员常说的“堆、栈”中的“栈”,往往就是虚拟机栈中的局部变量表。
3)局部变量表介绍
结合Oracle官网给出的介绍:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html,局部变量表存储三大类型的数据:
1.java基本数据类型
byte,char,short,int,float,long,double,boolean.
2.对象引用
即reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,可能是指向一个代表对象句柄或者其他与此对象相关的位置。
图1:
图2:
3.returnAddress
即指向一条字节码指令的地址。
关于所占空间,除了long和double类型的数据占用2个局部变量空间,其余的数据类型只占用1个,对象引用和returnAddress亦然。
由于对象一般存在于堆中,就内存中“栈”和“堆”引用,这个老话题,我找到了一张之前学JavaEE时候的老照片:
其中对于栈中这三种类型的数据引用的方式,可以参考上图,比如int date = 9和Test test = new Test(); 分别在栈中的存放,一个直接存放Int类型数据,一个则通过reference地址,指向了堆内存当中。关于这块的理解,可以参考这篇文章,很深入:http://www.cnblogs.com/wangjzh/p/5258254.html。
3.本地方法栈
1)是什么?
与虚拟机栈所发挥的作用相似,区别不过是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。
4.java堆
1)特性
1.它是java虚拟机所管理的内存中最大的一块。
2.线程共享,在虚拟机启动时创建。
3.存放对象实例,几乎所有的对象实例(以及数组)都在这里分配内存。
4.垃圾收集器管理的主要区域。
5.可以处于屋脊上不连续的内存空间中。
2)java堆细分
1.从内存分配的角度
Heap可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),无论哪个区域,存放的都是对象实例。
2.从内存回收的角度
Heap可以划分为:新生代、老年代。
如上图所示,新生代又可以细分为:Eden、From Survivor、To Survivor等空间,(上述Perm属于方法区)。
Heap这块知识,更多在GC上,放到后面的博客中小结。
5.方法区(Non-Heap)
1)是什么?
Method Area与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
2)特点
“永久代”,是HotSpot虚拟机设计团队把GC分代收集扩展至方法区的一种手法,这里在之后GC阶段,会详细讲解。
6.运行时常量池
1)是什么?
Runtime Constant Pool是方法区的一部分。用于存放编译器生成的各种字面量和符号引用。
7.直接内存
这部分不是虚拟机运行时数据区的一部分,但是这块会被经常使用。JDK1.4中新加入的NIO类,引入了一种基于通道与缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,之后通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
三、内存分配的常用策略
对象的内存分配,大致上讲,就是在堆上分配,对象主要分配在新生代的Eden区上,若启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能直接分配在老年代中。
一些常见的内存分配规则:
1.对象优先在Eden分配
2.大对象直接进入老年代
3.长期存活的对象将进入老年代
4.动态对象年龄判定
5.空间分配担保
这部分内容的细致讲解,将在后续的博客中进行。敬请期待!