因为文章 https://www.pdai.tech/md/java/jvm/java-jvm-x-overview.html 已经把 JVM 讲得非常详细透彻,这里不再重复造轮子,只是看完后的一些结论和问题(或许还没答案)总结,欢迎各位大佬在评论区留言并提问,我会不定期在评论区找出优质问题并回答,或许会提上本文正文。
注:不一定是面试题
看完类字节码详解的问题:
问:一个类的对象到底有多少字节
答: https://blog.csdn.net/qlmmys/article/details/53213857
看完类加载机制的结论:
一个类需要经过加载,连接,初始化 才能被使用,最后卸载,连接阶段又包含验证,准备和解析,准备阶段负责给 static 变量赋 0 值,解析阶段负责将符号引用转变为直接引用,可能在解析时遇到一个类名没有加载过,就又需要去加载那个类,加载完成后会在方法区保存类的二进制字节流,然后创建一个 class 对象保存在堆区。
类的加载阶段可以使用不同的加载器实现不一样的加载方式,一般是使用默认的双亲委派加载模式。
看完类加载机制后的问题:
问:类加载器是用来加载类的,那谁来加载类加载器呢
答:启动类加载器加载来加载其它类加载器
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
问:一个类何时会被加载,系统中那么多的类,不可能全部加载进来吧
一个类只有在用到的时候才会被加载,何为用到,即需要用到它的 Class 对象了的时候,例如 :实例化,使用静态方法或静态变量,反射,main 方法的类
问:静态代码块在什么时候执行,与静态变量的执行先后,final 变量在哪个阶段赋值
答:静态变量在准备阶段就可以在 方法区 中分配内存,并设置初始值,这个初始值是数据类型默认的 0 值,在初始化阶段再赋予显示指定的值
静态块在初始阶段执行,当静态块使用了静态变量需要静态变量先申明了再能使用,如下
static {
System.out.println(a);
}
static int a = 1;
// 这时候是报错的,Illegal forward refrence a 变量还未正确初始化
静态代码块与静态代码块是书写顺序执行的,代码块与代码块也是按照书写顺序执行的,尝试一下小例子
static {
System.out.println("什么时候加载");
}
static {
c = 3;
System.out.println(c);
}
关于 final 的使用,先看下面三个例子,可能颠覆你的认知,下面三种写法都是正确的
final int b ;
{
b = 2;
}
final int d;
public TestLoad(){
d = 4;
}
static final int c;
static {
c = 3;
System.out.println(c);
}
关于第一种和第二种,都是初始化实例变量,那谁先谁后呢,答案是代码块会比构造先执行,所以代码块会优先赋值,如果构造函数再次赋值会编译出错。
实例 final 常量是指在对象实例化后不可修改,因此可以在构造函数,代码块和申明时对其赋值
静态 final 常量是指在类初始化后不可修改,因此可以在静态代码块,申明时对其赋值
代码块无法对静态 final 常量赋值,因为静态 final 常量需要在类初始化的时候赋值,后面就不可以改了
子问:即然静态变量在准备阶段就分配内存并赋 0 值,那标记为 final 的变量如何在初始化阶段修改其值呢
猜(不知道答案):如果是 final 不会为其赋 0 值
问:main 方法如何执行的
答:首先 main 方法肯定要处于某一个类中,需要加载这个类,在初始化阶段会执行静态代码块,然后不会创建实例,也不会执行这个类的代码块,相当于调静态方法一样调起这个 main 函数所在的类
问:加载机制对应的一道非常经典的面试题
B 类继承自 A 类,同时有构造方法,代码块,静态代码块执行顺序是怎么样的
答:根据上面的结论,静态代码块在初始阶段执行,代码块优先于构造函数,在构造子类的时候需要先构造父类,所以执行顺序为 父类静态块,子类静态块,父类代码块,父类构造函数,子类代码块,子类构造函数
问:如果 springboot 项目中我有一个类和某一个 jar 包中的类同类名同包名,会加载哪个类; 两个 jar 包中有同包同类名的类,会加载哪个类
答:当前 classes 下有一个类和 jar 包中某个类同包同类名,则会使用当前 classes 下的类,这也是经常拿来覆盖 jar 包中的类的办法,如果两个 jar 包中有同包同类名的类,会优先加载 -classpath
选项中写在前面 jar 包中的类,这里会这样的原因是因为工作目录总是在 -classpath
中写前 jar 包的前面的,如果启动脚本不是写在前面那就不能这么干了。
问:tomcat 的类加载机制
答: https://www.jianshu.com/p/51b2c50c58eb
看完 JVM 内存结构时的结论:
内存结构分为:栈(Java 虚拟机栈,本地方法栈),堆(新生代 {伊甸园,幸存区from to },老年代),方法区,直接内存,程序计数器
看完 JVM 内存结构后的问题:
问:String str2 = new String("abc");
创建了几个对象
答:初级必问答,网上有各种五花八门的答案 ,我这里挑一个我认为正确的,不服欢迎来辩 https://www.cnblogs.com/zhaideyou/p/5875175.html
问:方法区,永久代,元数据区怎么理解
答:方法区是 JVM 规范,永久代和元数据区是其实现,逻辑上属于堆的一部分,但为了和堆进行区分,通常又叫非堆
因为越来越多的动态类生成,jsp 时代会有大量的 jsp 动态类,spring 的动态代理也会生成大量动态类,经常导致永久代内存溢出,所以在 1.7 开始,就开始把永久代的内存搬到其它地方,比如 1.7 版本 Jdk 就搬了 字符串常量池,这个不是空说的,有人做过验证https://blog.csdn.net/kdy527/article/details/86511693
移动的内容包含
- 符号引用被移到了native堆
- 池化string对象被移到了java堆
- Class对象、静态变量被移到了java堆
参考文章: https://blog.csdn.net/wuhenzhangxing/article/details/78224905
问:元数据区中存储了些什么信息,这个元空间是存储在哪里
答:jdk1.8 Metaspace 存储在 native memory ,不会受 jvm 堆大小的约束,以前永久代也不会,只受限制于系统的内存和系统位数(32 位最大 4G )
元数据区主要存储类的元数据信息了和类加载器信息
navtive memory 就是供 jvm 自身进程使用,主要保存这些信息
- 管理java heap的状态数据(用于GC);
- JNI调用,也就是Native Stack;
- JIT(即使编译器)编译时使用Native Memory,并且JIT的输入(Java字节码)和输出(可执行代码)也都是保存在Native Memory;
- NIO direct buffer。对于IBM JVM和Hotspot,都可以通过-XX:MaxDirectMemorySize来设置nio直接缓冲区的最大值。默认是64M。超过这个时,会按照32M自动增大。
- 对于IBM的JVM某些版本实现,类加载器和类信息都是保存在Native Memory中的。
参考文章 : https://blog.csdn.net/u013721793/article/details/51204001
问:一个类中,静态变量保存在哪里,方法保存在哪里,Class 对象保存在哪里,常量池保存在哪里,线程中创建的对象保存在哪里
静态变量本质和类对象是一起的,保存在堆中,有实验为证 https://blog.csdn.net/x_iya/article/details/81260154/
方法信息保存在元数据区
Class 对象保存在堆区 ,Class 信息保存在元数据区
常量池保存在堆区
问:在使用 jconsole 中看到的 code cache 是什么意思
https://www.jianshu.com/p/ba75d3c185cb 对于一些热点代码,会直接编译成机器码,用于快速执行
画了一张图,欢迎大神指正:
线程栈引用堆上的对象
参考资料汇总:
https://blog.csdn.net/kdy527/article/details/86511693
https://blog.csdn.net/wuhenzhangxing/article/details/78224905
https://blog.csdn.net/u013721793/article/details/51204001
https://www.iteye.com/blog/aoyouzi-2243929
https://www.pdai.tech/md/java/JVM/java-JVM-x-overview.html
接下来的 Java 内存模型,垃圾回收及收集器参数调优,都可以直接看原文,暂时没有什么好的问题提出