jvm面试原理
1、什么是JVMJVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),
1、什么是JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
2、JRE/JDK/JVM是什么关系
JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。
JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。
JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
3、如何将类加载到jvm
class文件是通过类的加载器装载到jvm中的!
Java默认有三种类加载器:启动类加载器、扩展类加载器、应用类加载器、自定义类加载器。
1) Bootstrap ClassLoader:负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类。
2) Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包。
3) App ClassLoader:负责记载classpath中指定的jar包及目录中class。
4、类加载器的工作过程
1) 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2) 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3) 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载。
4) 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载。
5) 如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
5、类加载详细过程
加载器加载到jvm中,接下来其实又分了好几个步骤:
1) 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象。
2) 连接,连接又包含三块内容:验证、准备、初始化。
① 验证,文件格式、元数据、字节码、符号引用验证。
② 准备,为类的静态变量分配内存,并将其初始化为默认值。
③ 解析,把类中的符号引用转换为直接引用。
3) 初始化,为类的静态变量赋予正确的初始值。
6、JVM的内存模型
基于jdk1.8画的JVM的内存模型。
1) 堆:存放对象实例,几乎所有的对象实例都在这里分配内存。
2) 虚拟机栈:虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。
3) 本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。
4) 方法区:存储已被虚拟机加载的类元数据信息(元空间)。
5) 程序计数器:当前线程所执行的字节码的行号指示器。
7、GC垃圾回收
GC (Garbage Collection)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停
首先,JVM回收的是垃圾,垃圾就是我们程序中已经是不需要的了。垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”。判断哪些对象“死去”常用有两种方式:
1)引用计数法-->这种难以解决对象之间的循环引用的问题。
2)可达性分析算法-->主流的JVM采用的是这种方式。
8、垃圾收集算法
1)标记-清除算法
“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
2)复制算法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
3)标记-整理算法
复制算法在对象存活率较高时就会进行频繁的复制操作,效率将降低。因此又有了标记-整理算法,标记过程同标记-清除算法,但是在后续步骤不是直接对对象进行清理,而是让所有存活的对象都向一侧移动,然后直接清理掉端边界以外的内存。
4)分代收集算法
把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
9、类的实例化顺序
1)父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行。
2) 子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行。
3) 父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行。
4) 父类构造方法。
5)子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行。
6)子类构造方法。
10、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式
双亲委托模型的重要用途是为了解决类载入过程中的安全性问题。当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
1)假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编 写的java.lang.Object类。
2)然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader的路径下找到java.lang.Object类,并载入它
Java的类加载是否一定遵循双亲委托模型?
1)在实际开发中,我们可以通过自定义ClassLoader,并重写父类的loadClass方法,来打破这一机制。
2)SPI就是打破了双亲委托机制的(SPI:服务提供发现)。
Android中的进程
(1) native进程:采用C/C++实现,不包含dalvik实例的进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的。如图 3,/system/bin/surfaceflinger、/system/bin/rild、procrank等就是native进程。
(2) java进程:Android中运行于dalvik虚拟机之上的进程。dalvik虚拟机的宿主进程由fork()系统调用创建,所以每一个java进程都是存在于一个native进程中,因此,java进程的内存分配比native进程复杂,因为进程中存在一个虚拟机实例。如图3,Android系统中的应用程序基本都是java进程,如桌面、电话、联系人、状态栏等等。
Android的 java程序为什么容易出现OOM
这个是因为Android系统对dalvik的vm heapsize作了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常(这个阈值可以是48M、24M、16M等,视机型而定),可以通过adb shell getprop | grep dalvik.vm.heapsize查看此值。
也就是说,程序发生OMM并不表示RAM不足,而是因为程序申请的java heap对象超过了dalvik vm heapsize。也就是说,在RAM充足的情况下,也可能发生OOM。
这样的设计似乎有些不合理,但是Google为什么这样做呢?这样设计的目的是为了让Android系统能同时让比较多的进程常驻内存,这样程序启动时就不用每次都重新加载到内存,能够给用户更快的响应。迫使每个应用程序使用较小的内存,移动设备非常有限的RAM就能使比较多的app常驻其中。但是有一些大型应用程序是无法忍受vm heapsize的限制的,后面会介绍如何让自己的程序跳出vm heap size的限制。
应用程序如何绕过dalvikvm heapsize的限制
对于一些大型的应用程序(比如游戏),内存使用会比较多,很容易超超出vm heapsize的限制,这时怎么保证程序不会因为OOM而崩溃呢?
(1)、创建子进程
创建一个新的进程,那么我们就可以把一些对象分配到新进程的heap上了,从而达到一个应用程序使用更多的内存的目的,当然,创建子进程会增加系统开销,而且并不是所有应用程序都适合这样做,视需求而定。
创建子进程的方法:使用android:process标签
(2)、使用jni在native heap上申请空间(推荐使用)
nativeheap的增长并不受dalvik vm heapsize的限制,从图6可以看出这一点,它的native heap size已经远远超过了dalvik heap size的限制。
只要RAM有剩余空间,程序员可以一直在native heap上申请空间,当然如果 RAM快耗尽,memory killer会杀进程释放RAM。大家使用一些软件时,有时候会闪退,就可能是软件在native层申请了比较多的内存导致的。比如,我就碰到过UC web在浏览内容比较多的网页时闪退,原因就是其native heap增长到比较大的值,占用了大量的RAM,被memory killer杀掉了。
(3)、使用显存(操作系统预留RAM的一部分作为显存)
使用 OpenGL textures 等 API , texture memory 不受 dalvik vm heapsize 限制,这个我没有实践过。再比如 Android 中的 GraphicBufferAllocator 申请的内存就是显存。
参考博客
更多推荐
所有评论(0)