JVM
[来源:维基百科]
程序计数器
- 程序计数器是当前线程执行的字节码的行号指示器;
- 程序计数器线程私有;
- 程序计数器是JVM 规范中唯一一个没有任何OutOfMemoryError 的区域;
虚拟机栈
- 线程私有,生命周期与线程相同;
- 虚拟机栈描述的是Java 方法执行的内存模型,每个方法在执行时会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。一个方法从调用到执行,就对应着一个栈帧从入栈到出栈的过程。对于一个栈帧来说执行引擎来说,如果一个方法调用链很长,调用其中一个方法,其他好多方法也可能处于执行状态,这时引擎会认为虚拟机栈顶的栈帧才是有效的,这个栈帧被称为当前栈,这个栈帧对应的方法称为当前方法,引擎的所有指令都是针对于当前栈帧进行操作的;
- 局部变量表,是一组变量值的存储空间,存储单位是slot。若是实例方法,则第0 个slot 存储的是指向所有实例对象的引用,在方法中可以通过this 来访问这个隐含的参数,接着存储的是参数,然后是方法内的局部变量;
- 操作数栈,方法执行之初为空,执行过程中会有各种字节码指令写入或者弹出值。它不通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的,比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。所以操作数栈又可以理解为“基于栈的执行引擎”;
- 动态链接,运行时转为直接引用;静态链接,解析时(符号引用)转化为直接引用;
- 当线程申请的栈空间大于虚拟机允许的深度时,会出现StackOverFlowError 异常;当虚拟机栈无法申请到足够内存时,会出现OutOfMemoryError 异常;
本地方法栈
同上,为native 方法服务;
堆
- 线程共享;
- 用来存储实例对象与数组对象;
- 由于现在有了逃逸分析技术,也可以将对象分配到栈上;
- “垃圾堆”,Java 堆是GC 的主要区域,主要采用分代回收,有年轻代,老年代;
- Java 堆,物理上可以不连续,逻辑上要求连续;
- 内存分配,碰撞指针(要求内存绝对规整,注意线程同步,采用CAS 原理+ 失败重试或者本地线程分配缓冲),空闲列表(内存不规整)。选择哪种分配算法取决于内存是否规整,是否规整取决于采用的GC 算法是否压缩;
- 对象的访问方法,句柄 + 直接访问;
- 堆空间不足时抛出 OutOfMemoryError;
方法区
- 线程共享;
- 用于存储已经被虚拟机加载的类的类信息,常量,静态变量,编译后的代码,运行时常量池(存储编译器生成的各种字面量与符号引用);
- class 文件中的常量池在类加载之后就被放入运行时常量池;相比之下,方法区的运行时常量池具有动态性;
- 常量可以在运行期间通过intern 加入到常量池中;
- 方法区空间不足时会抛出 OutOfMemoryError;
堆外内存(直接内存)
1. 直接内存可以减少IO 时的内存复制,零拷贝(不需要堆内存Buffer 拷贝一份到直接内存);
2. 无gc;
优点:
- 减少垃圾回收工作,垃圾回收会暂停其他工作(若使用多线程或者时间片方式,根本感觉不到);
- 加快复制速度,省略掉Buffer Copy To off-heap 过程;
缺点:
- 难以控制,若内存泄露,排查难度很大;
- 不适合存储复杂的对象;