什么是JVM?
有哪些好处?
索引越界可能导致程序覆盖其他程序内存中的代码
JVM是一套规范,有多种实现
JVM运行流程?
1.类加载器将java程序翻译为java字节码文件
2.运行数据区将字节码文件加载到内存,字节码文件是JVM规范的文件,不能直接运行,而是交给执行引擎运行
3.执行引擎中的解释器将字节码文件翻译为机器指令,交给CPU执行,执行过程中可能通过本地方法调用接口调用本地方法
类的生命周期?
加载:将class文件从磁盘导入内存
连接:生成虚方法表,确定每一个方法的地址
验证:验证字节码文件是否符合JVM规范等
准备:为类变量初始化默认值,如:0,false等
解析:将字符引用变为直接引用,直接指向内存中的方法
初始化:使用的时候对其初始化,比如在new的时候
使用
卸载:使用结束后被垃圾回收器回收
JVM的类加载机制?
JVM类加载器结构:
启动类加载器:加载jre中lib目录下的核心类库
拓展类加载器:加载jre中lib目录下的ext拓展目录中的类包
应用程序类加载器:加载classpath路径下的类包,主要是自己写的类
自定义加载器:加载用户自定义路径下的类包
JVM加载类过程:
先将类加载请求交给自定义类加载器,自定义类加载器将加载类请求交给应用程序类加载器,应用程序类加载器再将请求给拓展类加载器,拓展类加载器再把请求给启动类加载器,如果启动类加载器能够加载就由启动类加载器加载,不能的话逐级交给子(下级)加载器加载,直到加载成功。
以上的这种加载方式叫做类加载器的双亲委派机制。
双亲委派机制的好处:
1.保证类只加载一次,不重复加载。
2.一定程度保证了类加载的安全性,比如:用户自定义了一个String类,那么在加载的时候就会由启动类加载器加载核心类库中的String类而不是加载用户自定义的String类,防止由于用户篡改核心类库导致的不安全。
双亲委派机制可以被打破:tomcat的例子,在tomcat上由不同的web应用,如果不同的web应用需要加载同一个类,由于双亲委派不重复加载的特性就会导致只能有一个web应用能够加载该类,在这种情况下,应该打破。
tomcat通过在自定义实现类加载器中的loadClass
方法中覆盖默认的双亲委派行为实现。
对象的创建过程?
1.当接受到一条new指令时,首先在常量池中检查是否存在类的引用符号,判断类是否被加载,解析,初始化过,如果有进入下一过程;如果没有,执行类加载流程。
2.JVM为对象开辟内存空间(根据回收机制不同,选择指针碰撞或空闲列表的方式)。
什么是指针碰撞?什么是空闲列表?
3.为对象开辟内存后,将内存中的所有数据初始化为默认值,int为0,boolean为false等。
4.在对象头内设置信息,如:是哪个类的对象,属于哪一代,是否加锁(偏向锁,轻量级锁,重量级锁)等。
5.调用构造方法初始化对象,赋予对象有意义的值。
静态常量池(class常量池)和运行时常量池?
静态常量池:每个class文件里有一个class常量池,包含符号引用和字面量(数值型和字符型)。
运行时常量池:当class被加载时,class常量池中的符号引用和数值型字面量会被加载到直接内存方法区中的运行时常量池和数值型字面量,其中符号引用会被替换为直接引用,而字符型字面量则会被加载到队中的字符串常量池中,用来节省空间。
class常量池到运行时常量池的转变发生在类加载过程的解析阶段。
CPU飙高排查?
OOM原因及如何排查?
JVM如何实现多态?
在实现多态的类加载过程中,由于无法确定具体到底该使用哪一个方法,所以多态方法部分的符号引用并不会替换为直接引用,而是等到真正调用时通过调用对象类型来决定具体使用哪一个方法。通过查询虚方法表来查找对应方法。
为什么将永久代移除,而在内存里加入了元空间?
永久代在堆内存中,使用堆内存空间,较为局限,而元空间在操作系统管理的本地内存中,本地内存与虚拟机使用内存相隔离,不受堆内存大小限制,这也是解决永久代频繁导致OOM异常的方法。
什么是虚拟机栈?
虚拟机栈是线程执行时所占用的内存,一个栈由多个栈帧构成,栈帧对应于调用方法所使用的内存空间,但一个栈只有一个活动栈帧,对应着当前正在执行的方法。
栈帧的结构:
局部变量表
操作数栈
动态链接
方法出口
栈帧弹起就会释放内存,不需要垃圾回收。
栈帧大小和可活动线程数相互制约。
方法内局部变量是否线程安全?未逃出方法作用范围安全,否则需要考虑线程安全问题。
栈溢出,StackOverFlow异常,递归调用。
什么是堆?
线程共享的内存区域,主要用来存储类的实例对象和数组等,当堆内存满且无法扩展时,抛出OutOfMemory异常。堆内主要分为年轻代和老年代,年轻代分为伊甸园区和两个大小相等的幸存区,年轻代的数据经过一定次数的垃圾回收后仍然存在的将被转移到老年代。老年代用来存储存活时间长的数据。JDK8以前堆中还有一个永久代,但如果加载大量类容易大量占用堆内存,导致OOM异常,所以移出堆,放到本地内存中,叫做元空间,但元空间,方法区,永久代其实是类似的。
为什么使用直接内存的NIO读写性能比常规I/O更高?
常规I/O在数据读写时,比如一个从磁盘中读取文件的操作,需要在内核态将磁盘写入内核态缓冲区,内核态缓冲区的数据再写入用户态的java缓冲区,而使用直接内存的NIO则直接将磁盘文件写入直接内存,JVM可以直接操作直接内存,不需要在堆中开辟额外空间,同时减少了两个缓冲区间传输数据的时间,大大提高了数据读写的效率。