没准备好就不要冲动
以下只针对Java岗。来源主要是牛客的Java实习面经。下面的回答直接背就可以,需要一定的Java和Jvm基础,适合春招实习的同学,但是我会在每个问题下把有助于理解的博客贴出来。如果发现有问题欢迎私聊我或留言我会在下面更新
关于Java虚拟机,需要知道Jvm的内存结构,垃圾回收机制,内存分配策略,类的加载机制以及内存模型*(并发)*。以下是面试常考的问题,有时候还会问到一些Jvm的分析工具
虚拟机栈引用的对象,方法区类静态属性引用的对象,方法区常量引用的对象
三七互娱19年春招
华为19年社招,京东19年秋招本科
对于JDK1.6来说,Jvm的内存结构主要分为五部分,其中的堆和方法区*(对于HotSpot来说由永久代实现,包括常量池)*是线程共享的,虚拟机栈,本地方法栈和程序计数器是线程独有的;永久代在堆中
对于JDK1.7来说,Jvm的内存结构主要分为五部分,其中的堆*(包括常量池)和方法区(对于HotSpot来说由永久代实现)*是线程共享的,虚拟机栈,本地方法栈和程序计数器是线程独有的;五部分都在运行时数据区
对于JDK1.8来说,Jvm的内存结构主要分为五部分,其中的堆*(包括常量池)和方法区(对于HotSpot来说由元空间MetaSpace实现)*是线程共享的,虚拟机栈,本地方法栈和程序计数器是线程独有的;MetaSpace在直接内存中,不受Jvm堆的控制,其他四部分还在运行时数据区
有一点需要特殊注意,TLAB在堆中,分配时是线程独有的,使用时是线程共享的
废除永久代的原因主要是:1. 其他虚拟机不存在永久代概念*(官方准备融合 JRockit VM )*;2. 整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。同时可以加载的类会更多
程序计数器:如果是native方法,则计数器值为空;如果正在执行java方法,计数器记录的是正在执行的虚拟机字节码指令地址;Jvm规范中唯一没有规定任何OutOfMemoryError
异常情况的区域;线程上下文交换时记录保存在程序计数器中;选取要执行的指令,跳转,循环等的执行
虚拟机栈:描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。每个方法执行(不是创建)时都会创建一个栈帧(栈帧和栈不是一回事,栈帧只是栈的一段区域,有栈顶和栈底)。栈帧中存有局部变量表,操作数栈,动态链接,方法出口等信息,通过return和异常可以退出栈帧;该区域可能出现两种异常情况:StackOverflowError(线程请求的栈的深度大于虚拟机允许的)和OutOfMemoryError(虚拟机栈扩展时无法申请到足够的内存)(-Xss:)
本地方法栈:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一
堆:用来存放实例,是Jvm最大的一块区域,也是GC管理的主要区域。在JDK7及以前,分为新生代*( Eden 8、From Survivor 1、To Survivor 1 ),老年代和永久代(即方法区)。新生代与老年代是1:2;Java堆既可以是固定大小,也可以是可扩展的(通过-Xmx:256G和-Xms:256G实现。前者最大值,后者最小值。还有一个命令参数是-XX:+HeapDumpOnOutOfMemoryError,当发生该异常是dump堆快照)*,无法扩展时,抛出OutOfMemoryError
方法区,7及之前是永久代,之后是元空间。 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;抛出OutOfMemoryError
常用参数:
-XX:PermSize=N //方法区 (永久代) 初始大小
-XX:MaxPermSize=N //方法区 (永久代) 最大大小 java.lang.OutOfMemoryError: PermGen
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小 java.lang.OutOfMemoryError: MetaSpace
运行时常量池,6及以前在方法区中,之后再堆中。用于存放的编译期间形成的各种字面量和符号引用,在编译期形成,如声明为final的常量值等符号引用,类和接口的完全限定名等;当常量池无法申请内存时会抛出OutOfMemoryError
直接内存,这个不在Jvm的堆管理中,MetaSpace,Java中的nio,buffer都在直接内存中。通过-XX:MaxDirectMemorySize
猪场19年实习
JDK1.7及以前,堆中有方法区和运行时常量池。包括实例,常量,类信息
JDK1.8及以后,堆中有运行时常量池。有实例和常量,没有类信息
阿里19年秋招本科
内存结构是对于Jvm来说的,内存模型是Java Memory Model,针对于Java并发编程的
对于第二个问题来说,HotSpot将Jvm堆分为新生代和老年代,主要是用了分代回收算法。根据研究表明,大量对象都是"朝生夕死"的,根据这个特性,我们在新生代存储可能“朝生夕死”的对象,在老年代中存储存活时间比较长的对象,这样可以针对不同的存活时间进而选择更高效率的回收算法
在新生代中,因为有98%的对象朝生夕死,我们只需要很少的空间就可以进行复制算法,非常划算。在老年代中, 对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集
为什么Eden区不用标记-清除算法
三七互娱19年春招
因为新生代频繁创建对象
阿里19年秋招本科
OutOfMemoryError一般出现在堆和方法区,也会出现在运行时常量池中。一般情况下出现在堆中,可能是堆内存不够,需要调用-Xmx?G/M
来增加堆的大小。如果频繁出现这种情况*jstat -gcutil pid 1000
*,说明极有可能出现了内存泄露情况,此时需要使用参数-XX:HeapDumpOnOutOfMemoryError
dump堆内存的快照,然后借助工具进行排查。下面部分会详细说
阿里19年秋招本科
Class 文件需要加载到虚拟机中之后才能运行和使用,类加载过程主要分为三步:加载,连接*(验证,准备,解析)*,初始化
()
方法的过程。所有Java虚拟机实现必须在每个类或接口被Java程序首次主动使用才初始化,但类加载不一定,静态代码块在类初始化时执行
java.lang.reflect
包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化滴滴19年秋招本科,京东19年秋招本科
这里不是指类加载几个过程,类加载主要采用了双亲委派模型。
每一个类都有自己的类加载器, 在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass()
处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader
中。当父类加载器无法处理时,才由自己来处理。
A p p C l a s s L o a d e r → E x t e n s i o n C l a s s L o a d e r → B o o t s t r a p C l a s s L o a d e r AppClassLoader \rightarrow ExtensionClassLoader \rightarrow BootstrapClassLoader AppClassLoader→ExtensionClassLoader→BootstrapClassLoader
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改
如果多个类加载器加载同一个类,会出现什么情况
首先要先看这几个类加载器是否遵循双亲委派原则,如果遵循,那么对于Object类来说,只会有BootstrapClassLoader来加载。
如果这个类不能被三个内置类加载器加载,多个类加载器加载同一个类时,加载出来的类是不一样的
滴滴19年秋招本科
class文件中,紧接着魔数的四个字节存储的是 Class 文件的版本号:第五和第六是次版本号,第七和第八是主版本号。高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件
猪场19年实习
这里可以自己写一个类加载器,同时也可以说Tomcat中的类加载器
如果是自己实现类加载器,那么只需要覆盖ClassLoader这个抽象类就行。自定义类加载器,可以自定义类的查找来源,自定义加密,热部署等等
private ClassLoader getClassLoader() {
return new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadByte();
assert data != null;
return defineClass(name,data,0,data.length);
}
};
}
private byte[] loadByte() {
File file = new File("D:\\src\\classloader\\test\\String.class");
try(FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int size;
byte[] buffer = new byte[1024];
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
对于Tomcat的类加载器
字节跳动19秋招,阿里19年秋招本科,华为19年社招,京东19年秋招本科
这个题也可以理解为垃圾回收策略。
当Jvm判断对象不可达并且经过两次标记之后,就会通过GC算法进行回收。在新生代主要是复制算法,在老年代主要是标记复制和标记清楚算法。常见的垃圾收集器包括Serial,ParNew,parallel-scavenge,Serial Old,ParNew Old,CMS,G1
打印GC日志是-XX:+PrintGCDetails
华为19年社招
-XX:MaxTenuringThreshold
来设置对象年龄阈值-XX:PretenureSizeThreshold
,令大于该尺寸的对象直接进入老年代华为19年社招
Full GC是针对整个新生代、老生代、元空间的全局范围的GC。
触发full GC最主要的原因就是空间不足,有:创建大对象进入老年代时,如果老年代内存不足,则触发Full GC;常量池和元空间内存不足时,也会触发full GC;从新生代进入老年代时内存不足,触发Full GC;显式调用System.gc()
三七互娱19年春招
bigo19年秋招本科
标记清除是先标记再清除,容易产生大量碎片。标记整理算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存
三七互娱19年春招
不用手动管理垃圾回收,但是也造成了无法对垃圾进行管控
三七互娱19年春招
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
什么可以作为GC-roots的引用链
GC Roots的对象包括:static,final,native和局部变量所引用的对象
猪场19年实习
GC不止是对堆,还是直接内存。
调用System.gc()只是题型Jvm执行GC,而不是一定就能GC
阿里19年秋招本科
-XX:MaxTenuringThreshold
来设置对象年龄阈值-XX:PretenureSizeThreshold
,令大于该尺寸的对象直接进入老年代阿里19年秋招本科
JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
new Object()
,GC宁愿抛出OutOfMemory
也不会回收SoftReference
WeakReference
RhantomReference
。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收pdd19年秋招本科
有两种算法,分别是引用计数法和可达性分析算法
对象是否存活,需要两次标记,第一次标记通过可达性分析算法。如果没有GC Roots相连接的引用链,那么将第一次标记。如果对象的finalize()
方法被覆盖并且没有执行过,则放在F-Queue队列中等待执行*(不一定一定执行)*,如果一段时间后该队列的finalize()
方法被执行且和GC Roots关联,则移出“即将回收”集合。如果仍然没有关联,则进行第二次标记,进行回收
类和常量的废弃
bigo19年秋招本科
什么参数能够调整新生代的比例
华为19年社招
jps
(JVM Process Status): 类似 UNIX 的 ps
命令。用户查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息;jstat
( JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据;jinfo
(Configuration Info for Java) : Configuration Info forJava,显示虚拟机配置信息;jmap
(Memory Map for Java) :生成堆转储快照;jhat
(JVM Heap Dump Browser ) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果;jstack
(Stack Trace for Java):生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。三七互娱19年春招,pdd19年秋招本科,bigo19年秋招本科
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:
如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。久而久之,就会导致VM不断Full GC,但是却没有什么用
jps
/ ps -ef | grep java
来找到要查看的java pidjstat -gcutil pid 1000
来打印Full GC的频率来确定是否发生Full GCjmap -histo:live pid
,如果明显某个实例数比较多,则基本锁定是该实例内存泄露。如果找不出来,可以打印堆文件jmap -dump:live,format=b,file=heap.bin pid
滴滴19年秋招本科
jstat -class vmid
:显示 ClassLoader 的相关信息;jstat -compiler vmid
:显示 JIT 编译的相关信息;jstat -gc vmid
:显示与 GC 相关的堆信息;jstat -gccapacity vmid
:显示各个代的容量及使用情况;jstat -gcnew vmid
:显示新生代信息;jstat -gcnewcapcacity vmid
:显示新生代大小与使用情况;jstat -gcold vmid
:显示老年代和永久代的信息;jstat -gcoldcapacity vmid
:显示老年代的大小;jstat -gcpermcapacity vmid
:显示永久代大小;jstat -gcutil vmid
:显示垃圾收集信息;另外,加上 -t
参数可以在输出信息上加一个 Timestamp 列,显示程序的运行时间
比如看一个线程的回收情况,怎么看、其他的命令呢
字节跳动19年本科,bigo19年秋招本科
还会其他JVM命令吗
此处可以说下其他的新能调优工具如jstat,jamp,jps等等
也可以说Jvm启动时的参数,如-XX:newRatio -XX:SurvivorRatio :XX:+SerialGC -Xms: -Xmn 等等