预备
为了更好的理解类加载和垃圾回收,先要了解一下JVM的内存区域(如果没有特殊说明,都是针对的是 HotSpot 虚拟机。)。
Java 源代码文件经过编译器编译后生成字节码文件,然后交给 JVM 的类加载器,加载完毕后,交给执行引擎执行。在整个执行的过程中,JVM 会用一块空间来存储程序执行期间需要用到的数据,这块空间一般被称为运行时数据区,也就是常说的 JVM 内存。
JVM的不断在发展,所以内存区域的规范也在更新
看这个地方的时候,总是不自觉的联想到操作系统的虚拟内存空间,疑惑它们之间的区别,这个博文JVM内存是对应到操作系统内存_jvm内存和电脑内存的关系_ManimalW的博客-CSDN博客解释了我的疑惑。
线程私有部分
程序计数器:每个线程私有,是一块内存区域,存放下一个要执行的字节码指令的地址。程序计数器中存储的数据所占的空间不会随程序的执行而发生大小上的改变,因此,程序计数器是不会发生内存溢出现象(OutOfMemory)的问题
虚拟机栈:每个线程私有,存放栈帧,方法调用时入栈,结束时出栈
本地方法栈:和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
以上三者是线程私有,也就是每个线程都有这样一块区域只存放自己的东西。
线程共享部分
堆:heap,是垃圾回收的主要区域,从垃圾回收的角度来看,由于垃圾收集器基本都采用了分代垃圾收集的算法,所以会将堆细分。
在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:
- 新生代内存(Young Generation)
- 老生代(Old Generation)
- 永久代(Permanent Generation)
其中永久代是方法区的具体实现,《Java 虚拟机规范》规定了有方法区这么个概念和它的作用,具体实现靠各种虚拟机。hotspot的实现就是永久代,HotSpot 使用 GC分代来实现方法区内存回收。从jdk8开始变成了元空间,放在了本地内存中存放。
总结一下区别:
永久代和元空间是hotspot针对方法区不同的实现
永久代在堆空间中,元空间在本地内存
永久代变成元空间后,静态变量和字符串常量也被移动到了堆中存放
运行时常量池:在方法区中,在hotspot中就是元空间或者永久代中。运行时常量池用来动态获取类信息,包括:class文件元信息描述、编译后的代码数据、引用类型数据、类文件常量池等。
字符串常量池:这个存放字符串常量,创建字符串之前检查常量池中是否存在,如果存在则获取其引用,如果不存在则创建并存入,返回新对象引用。
对象创建的过程
1.类加载检查 检查类有没有被加载初始化过,如果没有 执行类加载过程
2.分配内存 为对象分配内存
3.初始化零值
4.设置对象头 例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
5.执行<init>方法
对象访问定位
直接指针
栈中的引用存放的就是对象的地址
基于句柄
堆中划分一块区域作为句柄池,池中存放的就是地址,栈中的引用存放的时句柄的地址
面试题
- 介绍下 Java 内存区域(运行时数据区)
- Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)
- 对象的访问定位的两种方式(句柄和直接指针两种方式)