Java 一 基本知识
Java 代码如何运行起来的
java 代码被编译成字节码指令,然后字节码指令一定会被一条一条执行,这样才能实现我们写好的代码执行的效果。
例如,我们写好的 ".java" 代码打包过程中,会把代码编译成 ".class" 后缀的字节码文件。JVM中的 "类加载器" 把编译好的 ".class" 字节码文件给加载到 JVM中,最后,JVM 会基于自己的字节码执行引擎,来执行加载到内存里的我们代码写好的那些类。他需要哪个类时,就会使用类加载器来加载对应的类,反正对应的类就在 ".class" 文件中。
JVM 类加载机制
一个类从加载到使用,一般会经历以下过程:
加载 -》验证 -》准备 -》解析 -》初始化 -》使用 -》卸载
一般在什么情况下会去加载一个类?就是在你的代码中用到这个类的时候,比如你有一个类(Kafka.class),里面有一个 "main()" 方法作为主入口。JVM进程启动后会先把这个类(Kafka.class)加载到内存里,然后主从 "main()" 方法的入口代码执行。接着如果遇到你使用了别的类,比如:“ReplicaManager”,此时就会从对应的 ".class" 字节码文件加载对应的类到内存里来。
简单说下类加载中的4个阶段:
(1)验证阶段:根据 Java 虚拟机规范,来校验你加载进来的 ".class" 文件中的内容,是否符合指定的规范。
(2)准备阶段:给类分配一定的内存空间,然后给他里面的类变量分配内存空间并设置一个默认的初始值。
(3)解析阶段:把符号引用替换为直接引用的过程。
(4)初始化阶段(核心):正式执行我们类的初始化代码。在准备阶段中,类变量只会开辟一个内存空间,然后赋一个默认初始值,真正的赋值逻辑在 "初始化" 阶段执行。
Java 有下面几种类加载器:
(1)启动类加载器 Bootstrap ClassLoader,主要负责加载我们机器上安装的 Java 目录下的核心类,在 Java 安装目录下的 "lib" 目录,这里就有 Java 最核心的一些类库,支撑 Java 系统的运行
(2)扩展类加载器 Extension ClassLoader, 跟启动类加载器类似,支撑 Java 系统的运行,在 Java 安装目录下的 "lib\ext" 目录
(3)应用程序类加载器 Application ClassLoader,这类加载器就负责去加载 "ClassPath" 环境变量所指定的路径中的类,可大致理解为取加载你写好的 Java 代码。
(4)自定义类加载器,还可以自定义类加载器,去根据你自己的需求加载你的类。
双亲委派机制
JVM 的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器是在第二层的,第三层是应用程序类加载器,最后一层是自定义类加载器。
双亲委派模型简单来说就是:先找父亲去加载,不行的话再由儿子来加载。这样的话,可以避免多层级的加载器结构重复加载某些类。
JVM 内存区域介绍
(1)存放类的方法区
JDK 1.8 前,方法区,1.8以后 叫做 "Metaspace",元数据空间。主要是放从 ".class" 文件里加载进来的类,还有一些类似常量池的东西放在这个区域里。
假设有一个 "Kafak.class" 类和 "ReplicaManager.class"类,这两个类加载到 JVM 后,就会放在这个方法区中,如图:
(2)执行代码指令用的程序计数器
当 JVM 加载类信息到内存后,实际就会使用自己的 "字节码执行引擎",去执行我们写的代码编译出来的代码指令,这时,JVM就需要一个特殊的内存区域用来 "记录当前执行的字节码指令的位置",也就是记录目前执行到了哪一条字节码指令。同时,JVM 是支持多个线程的,所以可能会开启多个线程并发执行不同的代码,就会有多个线程来执行不同的代码指令,因此,每个线程都会有自己的一个程序计数器,专门记录当前这个线程目前执行到了哪一条字节码指令。
(3)Java虚拟机栈
Java代码在执行的时候,一定是线程来执行某个方法中的代码。JVM 必须有一块区域来保存每一个方法内的局部变量等数据的,这个区域就是 "Java虚拟机栈"。每个线程都有自己的 Java 虚拟机栈,比如 main线程就会有自己的一个Java虚拟机栈,用来存放自己执行的那些方法的局部变量。线程每执行一个方法,就会对这个方法调用创建对应的一个"栈帧"。
栈帧包含了这个方法的局部变量表,操作数栈,动态链接,方法出口等东西。比如: main 线程执行了 main()方法,那么就会给这个 main() 方法创建一个栈帧,压入 main 线程的 Java 虚拟机栈。因为 main() 方法中有局部变量 "replicaManager" ,所以在 main()方法的栈帧里会存放对应的 "replicaManager" 局部变量。
(4)Java堆内存
用来存放我们在代码中创建的各种对象。
若我们在 main 方法里创建一个 ReplicaManager 对象。那么 Java 堆内存区域里会放入类似 ReplicaManager 的对象,且在线程执行 main() 方法代码的时候,在main方法对应的栈帧的局部变量表里,让一个引用类型的 "replicaManager" 局部变量来存放 ReplicaManager 对象的地址。相当于你可以认为局部变量表里的 "replicaManager" 指向了 Java 堆内存里的 ReplicaManager 对象。
(5)其他内存区域
在JDK很多底层API里,比如IO相关的,NIO相关的,网络Socket相关的。
JVM 垃圾回收
一旦 Java虚拟机栈帧中的方法执行完毕,其对应的栈帧就会从Java虚拟机栈里出栈。如果某个实例对象没有任何一个方法的局部变量指向它,也没有任何一个类的静态变量,包括常量等地方在指向他。那么JVM的垃圾回收线程就会把这个没人指向的实例对象给回收掉,从内存里清除。记住,我们在Java堆内存里创建的对象,都是占用内存资源的,而且内存资源有限。