(注: 本文中所指虚拟机均为HotSpot虚拟机)
一、Java虚拟机运行时数据
1. 程序计数器
(1)线程私有。
(2)可看作当前线程执行的虚拟机字节码指令的行号指示器。
(3)执行Java方法时值为正在执行的字节码地址;执行Native方法时值为空。
(4)无OutOfMemoryError情况。
2. Java虚拟机栈
(1)线程私有。
(2)存放局部变量表、操作数栈、动态链接、方法出口等信息。
(3)方法调用对应着栈帧的入栈、出栈。
(4)栈深度超过虚拟机允许的深度——StackOverflowError异常;动态扩展无法申请到足够的内存——OutOfMemoryError异常。
注:
- 局部变量表:
存放方法参数和局部变量。 - 操作数栈:
计算过程的临时存储空间。 - 动态链接:
存储在运行期间会转换为直接引用的符号。 - 方法出口:
包括正常返回出口和异常返回出口。
3. 本地方法栈
作用同虚拟机栈。
4. Java堆
(1)线程公有。
(2)存放对象实例。
(3)垃圾收集器主要管理区域。
(4)堆无法再扩展时——OutOfMemoryError异常。
5. 方法区
(1)线程公有。
(2)存储类信息、常量、静态变量、即时编译器编译后的代码。
(3)无法满足内存分配需求——OutOfMemoryError异常。
(4)包括运行时常量池。
- 运行时常量池:
存储编译期生成的各种字面量和符号引用,运行期间也可以将新的常量放入池中。会抛出OutMemoryError的异常。
二、Java对象探秘
1. 对象的创建
(1)遇到new关键字。
(2)检查参数(即new后面紧跟的类名)是否能在常量池中定位一个类的符号引用。
(3)如果能定位,检查类是否已经被加载、解析和初始化过。没有加载过则加载。
(4)检查通过后为新对象分配内存(从堆中划分)。
(5)虚拟机将分配的内存空间初始化为0(不含对象头)。
(6)将对象信息放入对象头中。
(7)调用构造函数或初始化文件对新对象进行初始化。
2. 对象的内存布局
- Mark Word:包括哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
3. 对象的访问定位
(1)使用句柄
(2)直接指针
三、垃圾收集算法
1. 引用计数算法
基本思想:给对象中添加一个引用计数器。每引用这个对象一次计数器就加一;每引用失效一次就减一。
非常简单的一种算法,即高效又便于实现。但是很可惜,这种算法有一种天然的缺陷:当两个对象相互引用时,两个对象都无法被回收。
A a = new A();
B b = new B();
a.friend = b;
b.friend = a;
a = null;
b = null;
System.gc();
从上述例子中我们可以看出,对象a和对象b都已经不再使用,但是他们中的相互引用并没有失效,即a.friend ≠ null,因此双方的计数器都不会为0,从而导致两个对象均无法回收。
2. 可达性分析算法
基本思想:从“GC Roots”开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC roots没有任何引用链时,就认定其失效。
补充:可以作为GC Roots的对象
- 虚拟机栈中的本地变量表中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中Native方法中引用的对象。
3. 对象的最后一次自救机会
在被回收以前,对象还有最后一次拯救自己的机会!
众所周知,Java类在被回收前会触发一次finalize函数,如果在此函数运行时,对象能成功呼叫一名外援(引用链上的任何一个对象),并使其成功引用自己,这个对象就可以“死里逃生”(当然触发过的就不会再次触发了,这也意味着自救的小把戏只能用一次)。
@Override
protected void finalize throws Throwable{
super.finalize();
// 某个引用链上的对象引用一下这个类
}
4. 回收方法区
主要回收内容:废弃常量和无用的类。
- 废弃常量:没有任何对象引用常量就回收。
- 无用的类:
(1)类的所有实例被回收。
(2)加载类的ClassLoader已回收。
(3)对应的java.lang.Class对象没有被引用,并且无反射访问此类的方法。
同时满足上述三个条件就会被回收。
5. 垃圾收集算法
- 标记清除算法:标记所有需要回收的对象,标记完成后回收。
缺点:
(1)效率低。
(2)产生大量不连续内存碎片。 - 复制算法:将可用容量分为相等的两块,一块用完就让依旧存活的对象搬家到另一块上,然后将原内存块全部回收。
缺点:内存利用率低。
注: 复制算法升级版:
IBM研究表明,新生代中的对象98%是“朝生夕死”的。因此可以将内存划分比例调整为8:1:1 (Eden:Survivor1:Survivor2)。
使用方法:每次使用一个Eden和一个Survivor,回收时将存活的对象搬家到另一个Survivor上,然后将已使用的Eden和Survivor清空。如果Survivor空间不足,则需要老年代等其他内存进行分配担保(空间不足时,利用担保者的内存进行存储)。 - 标记-整理算法:标记所有需要回收的对象,标记完成后让存活对象向一端移动,然后清除其他内存。
- 分代收集算法:根据对象存活周期将内存分为不同几块,然后在块上再采用最适合的收集算法。
四、垃圾收集器
1.Serial收集器
特点:进行垃圾收集时必须暂停其他所有的工作线程。
2.ParNew收集器
特点:Serial收集器的多线程版本。
3.Parallel Scavenge收集器
特点:达到一个可控制的吞吐量。
注:吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间)
4.Serial Old收集器
特点:Serial的老年代版本。
5.Parallel Old收集器
特点:Parallel Scavenge的老年代版本。
6.CMS收集器
特点:重视服务响应速度希望系统停顿时间最短。基于“标记——清除”算法实现。
7.G1收集器
特点:并发、分代收集、空间整合、可预测的停顿。