JVM学习整理(纯干货,适合初学者)
一.java 运行时数据区
1.程序计数器
程序计数器是一块较小的内存空间.在虚拟机的概念模型中,字节码解释器的工作就是通过改变程序计数器的值来选取下一条需要执行的指令,分支,循环,跳转,异常处理,线程恢复等基础功能,都需要依赖这个程序计数器.
2.java虚拟机栈
虚拟机栈是用来描述java方法执行时的内存模型,每个方法在执行时都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程.
经常有人把Java内存区划分为堆内存(Heap)和栈内存(Stack),但实际上这种分法比较粗略,其中堆所指的后面会描述,而栈就是现在所讲的虚拟机栈,或者说虚拟机栈中局部变量表的部分
局部变量表中存放了编译期可知的各种基本数据类型,对象引用和returnAddress类型(指向了一条字节指令的地址)
局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小.
3.本地方法栈
本地方法栈与虚拟机栈所发挥的作用是很相似的,区别不过是虚拟机栈为虚拟机执行java方法(也就是字节码)服务 , 而本地方法栈则为虚拟机使用到的Native方法(Java调用非java代码编写的方法)服务 , 不过有的虚拟机直接把本地方法栈和java虚拟机栈合二为一(比如Sun HotSpot虚拟机)
4.java 堆
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例
同时堆也是垃圾回收器管理的主要区域,所以很多时候也被称为GC堆,由于现在垃圾回收都采用分代收集算法,所以java 堆可以细分为 : 新生代和老年代
根据java虚拟机规范的规定,java 堆可以处理物理上不连续的内存空间,只要逻辑上处于连续的即可.如果需要扩展的话可以通过-Xms和Xmx控制.
5.方法区
它用来存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,对于习惯HotSpot虚拟机上开发,部署程序的开发者来说都更愿意把方法区称为"永久代",这仅仅是因为HotSpot 虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已
6.java虚拟机运行时数据区图
其中,虚拟机栈,本地方法栈,程序计数器的生命周期随线程而生随线程而灭,每个线程都有一个独立的计数器,互不影响,线程私有
二.HotSpot虚拟机对象探秘
1.对象的创建
当虚拟机遇到一条New指令时,首先将去检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载,解析,初始化过.如果没有必须先执行相应的类加载过程(后面会讲到).
当类加载检查通过后,接下来虚拟机将为新生对象分配内存空间,这个对象所需要的大小会在类加载完成后便完全确定.假设Java堆中内存时绝对规整的.所有用过的放在一边,没有用过的放在另一边,中间放这个指针作为分界点指示器,那么分配内存就仅仅时把指针向空闲空间那边移动相当于对象大小相等的距离,这种方式称为指针碰撞.,如果java 堆中内存并不是规整的,那么虚拟机就必须维护一个列表,用来记录那些内存时可用的.在分配的时候,在可用的内存空间上分配一块空间给对象实例,这种分配方式称为空闲列表
但是在并发的情况下,也并不是线程安全的,有可能出现正在给A分配内存时,指针还没有来的急修改,对象B又同时使用了原来的指针来分配,那么解决这两种问题有两种方法,一种是采用CAS配上失败重试的方式来保证更新操作的原子性.另一种是把内存分配动作按照线程划分在不同的空间之中,既每个线程在java 堆中预先分配一块内存,称为本地线程分配缓冲(TLAB).虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定