文章目录
- 引言
- JVM是什么?
- 1. JVM内存划分
- 2. 对象如何在JVM中创建
- 2.1 内存分配
- 2.2 创建对象步骤
- 3. JVM类加载流程
- 3.1 双亲委派
- 总结
引言
Java开发人员在面试中基本都会被问到关于JVM的问题。想要成为高级的开发人员,了解和学习Java运行的原理和JVM是必不可少的,在解决疑难杂症上会很有帮助。本文会介绍在面试和实际工作中需要了解的主要的知识点。
JVM是什么?
JVM(Java虚拟机)。JVM的主要作用是在各种硬件平台上解释并执行Java字节码(.class文件),从而使得Java程序能够在不同的操作系统和架构上无需重新编译即可运行。这是Java“一次编写,到处运行”的核心所在。
1. JVM内存划分
JVM主要被划分成 堆、方法区(元空间)、虚拟机栈、本地方法栈、程序计数器五个部分,主要功能如下:
Heap(堆):
堆用于分配对象的实例以及数组的内存,线程共享,用来存放对象实例,也是垃圾回收(GC)的主要区域。
一般来说堆内又分为新生代和老年代,新生代又分为:Eden区和Surviver1和Surviver2区;
方法区:
方法区也可以称之为永久区,它主要储存的是已经被java虚拟机加载的类信息、常量、静态变量;Jdk1.8以后取消了方法区这个概念,称之为元空间(MetaSpace);
虚拟机栈:
虚拟机栈主要用于存储栈帧 ,线程私有,每一个方法在执行的时候都会创建一个栈帧,栈帧中用来存放(局部变量表、操作数栈 、动态链接 、返回地址)
本地方法栈:
本地方法栈和虚拟机栈类似,不同的是虚拟机栈服务的是Java方法,而本地方法栈服务的是Native方法。
PC程序计数器:
PC,指的是存放下一条指令的位置的一个指针。它是一块较小的内存空间,且是线程私有的。由于线程的切换,CPU在执行的过程中,需要记住原线程的下一条指令的位置,所以每一个线程都需要有自己的PC。
2. 对象如何在JVM中创建
2.1 内存分配
- 对象优先分配在Eden区,如果Eden区没有足够的空间进行分配时,虚拟机执行一次MinorGC。而那些无需回收的存活对象,将会进到Survivor 的 From 区(From 区内存不足时,直接进入 Old 区)。
- 大对象直接进入老年代(需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
- 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄(Age Count)计数器,如果对象经过了1次Minor
GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,直到达到阀值(默认15次),对象进入老年区。
2.2 创建对象步骤
创建对象主要分为5个步骤类加载检查、分配内存、初始化零值、设置对象头、执行init方法
①类加载检查:
虚拟机遇到 new 指令时,⾸先去检查是否能在常量池中定位到这个类的符号引⽤,并且检查这个符号引⽤代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执⾏相应的类加载过程。
②分配内存:
在类加载检查通过后,接下来虚拟机将为新⽣对象分配内存。
③初始化零值:
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值,这⼀步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使⽤,程序能访问到这些字段的数据类型所对应的零值。
④设置对象头:
初始化零值完成之后,虚拟机要对对象进⾏必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运⾏状态的不同,如是否启⽤偏向锁等,对象头会有不同的设置⽅式。
⑤执⾏ init ⽅法:
从虚拟机的视⻆来看,⼀个新的对象已经产⽣了,但从Java 程序的视⻆来看, ⽅法还没有执⾏,所有的字段都还为零。所以⼀般来说(除循环依赖),执⾏ new 指令之后会接着执⾏⽅法,这样⼀个真正可⽤的对象才算产⽣出来。
3. JVM类加载流程
过程:加载、验证、准备、解析、初始化
加载(Loading):
加载是指读取类的.class文件,将其转化为二进制数据流,再将这些数据转化成方法区中的运行时数据结构。
验证(Verification):
验证阶段确保字节码文件符合JVM规范,防止加载含有错误的类,保障类的正确性和安全性。
准备(Preparation):
准备阶段为类的静态变量分配内存并在方法区中设置默认初始值,但并不执行任何初始化代码。
解析(Resolution):
解析阶段将常量池中的符号引用转换为直接引用,即将类、字段、方法等符号引用解析为具体内存地址。
初始化(Initialization):
初始化是最具活力的一个阶段,它负责执行类构造器 () 方法,用于初始化类变量和其他静态成员,此时类才真正具备对外提供服务的能力。
3.1 双亲委派
JVM的双亲委派模型是指每当一个类加载器需要加载类的时候,它会先委托它的父加载器去尝试加载这个类,只有在父加载器无法加载这个类的情况下,才会由原始的类加载器尝试去加载。这个模型的主要目的是为了保证类的独立性和防止类的重复加载。这里的双亲不能以父母来理解,而是以自下而上的委托父类加载器来理解。
破坏双亲委派机制:
可以⾃⼰定义⼀个类加载器,重写loadClass方法;loadClass方法主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中。
总结
JVM内存划分及各区域的作用都需要了解,类加载和双亲委派面试常客,也建议学习加了解,学习里面的设计思想。后续会介绍JVM垃圾回收相关的知识。