Java 程序运行机制步骤
- 首先编写 Java 源代码,源文件的后缀为 .java
- 利用 JVM 编译器将源代码编译成字节码文件,字节码文件的后缀名为 .class
- 由 JVM 解释器运行字节码文件,将字节码文件解释成当前机器能够看懂的二进制数据
- 进行类的转载(加载、链接、初始化)
- 将数据读入到内存中,将不同的数据(变量)放入运行时数据区中的不同区域内
JVM 运行时数据区
- 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,JVM 解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成
- Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息,生命周期与线程一致,每个方法在执行时都会创建一个栈帧,每个方法从开始至结束都对应入栈与出栈的过程
- 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的,Native method(本地方法)特点是与某些硬件交互
- Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存,主要存放对象的实例与数组
- 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据,JDK1.8 之后,方法区在本地内存当中,一般不会出现内存不足的问题
Java 垃圾回收机制
在 Java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
Java 中都有哪些引用类型
- 强引用:发生 gc 的时候不会被回收,例如
Object obj = new Object(),只要 obj 指向堆中的 Object 实例,即使内存不足,该对象也不会被 gc - 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收
- 弱引用:有用但不是必须的对象,在下一次GC时会被回收
- 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知
JVM 如何判断当前对象是否要被 GC
- 引用计数器法:
为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题。 - 可达性分析算法:
从 GC Roots 作为对象开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。
JVM 的垃圾回收算法
- 标记-清除算法
该算法是标记可回收的对象,再清除这些对象,该算法实现简单,不需要对象进行移动,但是效率不高,会产生大量不连续的内存碎片。 - 标记-整理算法
该算法标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。该算法解决了内存碎片的问题,但仍需要进行局部对象移动,一定程度上降低了效率。 - 复制算法
该算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。该算法实现简单、运行高效,不用考虑内存碎片,但使得内存的利用率不高。 - 分代回收
堆中的新生代一般用复制算法,因为新生代中会有大量对象死亡,少量对象存活。
老生代中的对象存活时间较长,使用标记清除算法或标记整理算法。
类加载器有哪些
- 启动类加载器(Bootstrap ClassLoader):用来加载java核心类库,无法被java程序直接引用。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。用户自定义类加载器,通过继承java.lang.ClassLoader类的方式实现。
类装载的执行过程
- 加载:将类的字节码文件读入到内存中,并为之创建一个 java.lang.class 对象的过程
- 链接:
- 校验:确保当前 class 文件中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全
- 准备:给类中的静态变量分配内存空间
- 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址
- 初始化:对静态变量和静态代码块执行初始化工作,为静态变量赋值
- 使用:执行代码中的操作
- 卸载:JVM 通过 GC 将类信息与相关实例数据从 JVM 内存区域中移除
双亲委派加载机制
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。使用该机制可保证类被成功加载,还可保障一个特定的类被加载一次,避免重复加载。