Learn JVM
1. JVM 基础
1.1 Java从编码到执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eQguryO5-1634915025194)(https://files.mdnice.com/user/20547/c67ccb8e-5c16-414a-a11b-7ca574cf896a.png)]
Java-跨平台的语言:一处编译,可以在运行JVM的任何平台运行
JVM-跨语言的平台:无论什么语言,只要按照JVM的规范编译成符合Class文件的规范的文件,既可以在JVM上运行
1.2 JAVA&JVM文档
查看Java语言和JVM规范,可以查看官方的文档,访问官网查看需要的版本即可
Java SE Specifications (oracle.com)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XA0zwdSZ-1634915025197)(D:\OneDrive\桌面备份\typora-user-images\1633328816693.png)]
1.3常见的JVM的实现
我们使用的JVM可以控制台查看:
2. CLASS文件解读
2.1 如何将java源码生成.class文件
使用命令行,我们可以使用javac指令将编辑好的java文件编译成.class文件,当然也可以使用IDE工具进行编译生成.class文件
但是我们使用IDE工具打开的.class文件,工具会帮我们反编译,所以要查看原格式文件需要借助插件,这里我们使用BinED
装好之后右击.class文件,选择 Open As Binary即可以16进制,二进制,或者八进制,十进制查看
下面我们以16进制查看.class文件然后分析,下图是我们上面简单的代码编译后生成的.class文件的字节码code
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmSShiSp-1634915025202)(D:\OneDrive\桌面备份\typora-user-images\1633355532799.png)]
后面为了方便我们更好的阅读class文件,选择另一个IDEA的插件:jclasslib
鼠标放在要查看class源码文件内部,在工具栏View中选择Show Bytecode With Jclasslib,打开之后如下图所示,方便查看里面的内容
2.2 CLASS文件的格式
这里实验使用的是JDK1.8版本,查看JAVA官网提供的手册
https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf
第四章可以看到其中对于ClassFile结构的定义,源文件描述如下:
注: u4 表示4个字节,对应到.class文件可以确认前面的4个字节"CA FE BA BE"是这所谓的magic
2.2.1 magic
0x CAFE BABE
然后再往下看这个class文件
2.2.2 minor_version&major_version
minor_version: 0x0000
major_version: 0x0034
对应的10进制的值为52,然后我们查看文档中major versions format 可以看出对应的为java 8
2.2.3 constant_pool_count
0x0010 = 16
常量池数量
2.2.4 constant_pool[]
2.2.5 常量池的类型
下面开始分析class文件常量池部分
0x0A 对应十进制的10,查表可以看出tag 10为Constant_methodref
3. Class加载器ClassLoader
一个类,编译后生成class文件在硬盘上,需要先将class文件加载到内存,加载过程包括以下几步:
1. Loading
2. Linking:
Verification
Preparation,class静态变量赋值默认值
Resolution,静态变量赋值,初始化
这里先来看Loading过程
3.1 类加载器
3.1.1 双亲委派加载机制
所谓的父加载器,并不是加载器的父类加载器,也不是类加载器的加载器,只是类加载时的关系
为什么要有双亲委派这种类加载机制?主要是为了安全(核心类库中的类,防止自定义然后加载),以及效率(已经加载过的类不被重复加载),
所谓的双亲委派:
加载一个类时,先去查看是否已经加载,如果没有加载过,则让父加载器查看是否已加载,逐级向上,到顶级父加载器时也都没加载过则从上向下逐级尝试进行加载,知道加载完成,如果没有最终没有完成加载抛出异常
类加载的源代码如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
3.1.2 类加载器的范围
3.1.3 自定义类加载器
继承ClassLoader类,重写loadClass(String name) 方法
示例:
package cn.qiyuandata.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/**
* Title:MyClassLoader01
* <p>
* Desc:
* <p>
* At 2021/10/10
*
* @author ZhaoJu
*/
public class MyClassLoader01 extends ClassLoader{
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
File f = new File("G:/document/code/Java/LearnJvm/out/production/LearnJvm/", name.replace(".", "/").concat(".class"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int b = 0;
while ((b=fis.read()) !=0) {
byteArrayOutputStream.write(b);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
fis.close();//可以写的更加严谨
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
//throws ClassNotFoundException
return super.findClass(name);
}
}
使用举例:
热部署,一个类完成编辑编译后需要自动把新的class加载进内存实现替换,需要定义加载器
4. 编译
默认为混合模式,可以在启动参数设置编译模式,可以选择解释模式,或纯编译模式,对应如下
5. 初始化
静态变量赋默认值
静态变量赋初始值
看下面一段代码,Test类里有静态变量a,初始值为2,那下面一段代码的
package cn.qiyuandata.jvm;
public class InitTest01 {
public static void main(String[] args) {
System.out.println(Test.a);
}
}
class Test{
public static Test test = new Test();
public static int a =2;
private Test(){
a++;
}
}
这段代码输出的结果是几?答案是2
为什么呢,因为Test这个类在被调用时,先加载进来,然后声明test变量内存,这时test为null,a为默认值0
接下来调用构造方法a++,得到a=1
然后在执行a赋初始值a=2
所以最终结果为a=2
那么再来看下面一段代码呢,a与test的声明换了下位置,结果是几呢?
package cn.qiyuandata.jvm;
public class InitTest01 {
public static void main(String[] args) {
System.out.println(Test.a);
}
}
class Test{
public static int a =2;
public static Test test = new Test();
private Test(){
a++;
}
}
结果是3,为什么呢?
因为加载class后a赋默认值0,然后初始化为2
然后test声明为null
再然后执行构造方法,a++,值变成了3
以上是关于静态变量,接下来在讨论下关于成员变量的初始化
成员变量在new对象时进行初始化
初始化也是分为两步,先分配空间,声明变量,赋默认值,然后再赋初始值