Jvm 相关文章读后感包含一些我的面试经验

因为文章 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 自身进程使用,主要保存这些信息

  1. 管理java heap的状态数据(用于GC);
  2. JNI调用,也就是Native Stack;
  3. JIT(即使编译器)编译时使用Native Memory,并且JIT的输入(Java字节码)和输出(可执行代码)也都是保存在Native Memory;
  4. NIO direct buffer。对于IBM JVM和Hotspot,都可以通过-XX:MaxDirectMemorySize来设置nio直接缓冲区的最大值。默认是64M。超过这个时,会按照32M自动增大。
  5. 对于IBM的JVM某些版本实现,类加载器和类信息都是保存在Native Memory中的。

参考文章 : https://blog.csdn.net/u013721793/article/details/51204001

画了一张图,欢迎大神指正:

线程栈引用堆上的对象

参考资料汇总:

https://blog.csdn.net/kdy527/...

https://blog.csdn.net/wuhenzh...

https://blog.csdn.net/u013721...

https://www.iteye.com/blog/ao...

https://www.pdai.tech/md/java...

接下来的 Java 内存模型,垃圾回收及收集器参数调优,都可以直接看原文,暂时没有什么好的问题提出
我的博文大纲:https://blog.csdn.net/sanri1993/article/details/52201255

你可能感兴趣的