读过几遍周志明老师的《深入理解Java虚拟机:JVM高级特性与最佳实践》获益匪浅,但是关于一些问题经常忘记又需要重新翻阅。现在将其中内存虚拟机相关章节的内从自己浓缩总结一下,以供参阅。
可以在我的博客http://haiyangjiajian.com/交流更多相关内容。
1. Java内存区域介绍
- 程序计数器:每个线程独有,如果线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码地址,如在执行native方法,则计数器的值为空。唯一没有规定任何OutOfMemoryError的区域
- 虚拟机栈:每个线程独有的,每个方法被执行的时候都会同时创建一个栈帧用于存储局部表量表,操作栈,动态链接,方法出口等。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。局部变量表存放了编译器可知的基本数据类型如int等和对象引用和returnAddress类型(指向一条字节码指令的地址)。局部变量表所需要的内存空间在编译期间完成分配。存在两种异常。StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
- 本地方法栈:和虚拟机栈类似。虚拟机栈执行java方法,本地方法栈执行Native方法。有些虚拟机会如Sun HotSpot会把二者合二为一。
- Java堆:被所有线程共享,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例。Java堆可以出于物理上不连续的内存空间。会抛出OutOfMemoryError异常
- 方法区:被所有线程共享,存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。垃圾回收很少出现,主要针对常量池的回收和类型的卸载。运行时常量池是方法取得一部分,Class文件除了有版本、字段、方法等信息,还有常量表用于存放编译期生成的各种字面量和符号引用。类加载后这部分内容放到运行时常量池中。
2. 对象访问两种方式
- 句柄:Java堆中会划分出一个句柄池,reference中存储的是对象的句柄地址,句柄中包含真实地址。对象被移动时只需要改变句柄中实例数据指针,reference不需要修改。
- 直接指针:速度更快。
3. 垃圾回收
问题1:寻找一个对象是否存活的方法
- 引用计数法:无法解决对象相互循环引用的问题。
- 跟搜索算法:能否到GC Roots。如果能则存活,不能则应该回收
问题2:回收算法
- 标记清除算法:首先标记处所有需要回收的对象,在标记完成后统一回收掉被标记的对象。存在效率低和空间内存碎片的问题。
- 复制算法:现在商用虚拟机用来回收新生代。将内存分为较大Eden和两块较小Survivor,每次使用Eden和一块Survivor。回收时,Eden和Survivor中还存活着的一次性拷贝到另外一块Survivor,同时清理Eden和用过的Survivor。默认8:1:1。
- 标记-整理算法:适用于老年代,让所有的存活的对象都向一段移动,然后直接清理掉边界以外的内存。
现在整体采用的是分代收集算法。大对象直接进入老年代,长期存活的对象进入老年代