文章目录
参考 jvm学习《收藏系列》
1 九大部分
1.1 类加载子系统
负责从文件系统或者网络中加载Class信息,加载的信息存放在方法区。
1.2 方法区
存放类信息、常量信息、常量池信息、静态变量、即时编译器编译后的代码等数据。所有线程共享。如果系统定义的类太多,将会导致方法区溢出
1.3 堆
也叫GC堆,最主要的工作区域,存放实例对象以及数组。
1.4 直接内存
并不是jvm运行时数据区的一个部分。NIO库以内需Java程序使用直接内存,从而提高性能,读写频繁的场合和考虑使用。
1.5 jvm栈
每个线程私有,描述的是Java方法执行的内存模型,每个栈帧保存局部变量 、方法参数、Java方法调用、返回值等。
1.6 本地方法栈
类似于jvm栈。最大的不同是本地方法栈用于本地方法调用。JVM允许Java直接调用本地方法(例如C语言)。
1.7 垃圾回收系统
核心之一。开发人员无需手工清理。
1.8 PC寄存器
程序计数器。线程私有。看作当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条要执行的字节码。
1.9 执行引擎
负责执行虚拟机的字节码。
2 堆、栈、方法区的联系
堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
栈解决程序的运行问题,如何处理数据。
方法区则是辅助堆和栈的一块永久区(Perm),解决堆和栈信息的生成,是先决条件。
例如:
创建一个新的对象User,User类的信息都在方法区之中。
User类被实例化后的对象存到了Java堆之中
3 Java堆的不同结构
根据垃圾回收机制的不同,最为常见的就是将堆分为新生代和老年代。
- 新生代:存放新生的对象或者年龄不大的对象
- 老年代:存放老年对象
新生代又分为eden区、s0区、s1区。
- s0和s1也被称为from和to区域,他们是两块大小相等并且可以互相转换角色的空间。
- 大多数情况(可能会进入TLAB区域),eden存放刚实例化的对象。假设有一个user对象,在一次新生代回收后,如果还存活,则会进入s0或者s1。之后每经历一次新生代回收,如果对象存活那么他的年龄就加一,当对象达到一定年龄时,则进入老年代。
4 对象已经死了吗
4.1 引用计数法
所有JVM都不使用本方法了。
对于创建的每一个对象都有一个与之关联的计数器,这个计数器记录着该对象被使用的次数,垃圾收集器在进行垃圾回收时,对扫描到的每一个对象判断一下计数器是否等于0,若等于0,就会释放该对象占用的内存空间,同时将该对象引用的其他对象的计数器进行减一操作。
很难解决对象之间的相互循环引用。
例如如下代码:
class TestReferenceCount {
public Object instance = null;
public void test() {
TestReferenceCount a = new TestReferenceCount();
TestReferenceCount b = new TestReferenceCount();
a.instance = b;
b.instance = a;
a = null;
b = null;
}
}
4.2 标记清除法
分为标记和清除两个阶段。
产生空间碎片问题:垃圾回收后的空间不是连续的,不连续的内存的工作效率低于连续的空间内存。
4.3 复制算法
用于新生代。
将内存分为两块,每次只使用其中的一块,在垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后清除之前正在使用的内存块中的所有对象。反复交换两块内存的角色,完成垃圾收集。
4.4 标记压缩法
用于老年代。
也叫标记整理法。在标记清除法的基础上,把存活的对象压缩到内存的一端,之后进行垃圾清理。
4.5 为什么老年代和新生代使用不同的算法?
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。
新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
老年代的对象存活率高,故垃圾收集不会频繁执行。因为老年代中对象存活率高、也没有额外空间对他进行分配,就必须使用“标记—清理”或者“标记—整理”算法进行回收。使用复制算法则需要进行较多的复制操作,效率会变低。