虚拟机相关知识--面试专用

class文件


能够被JVM识别,加载并执行的文件格式,记录了一个类文件的所有信息

生成一个class文件,可以通过两种方式:

  1. 通过IDE自动帮我们build
  2. 手动通过javac去生成class文件

class文件格式详解:

  1. 一种8位字节的二进制流文件
  2. 各个数据顺序紧密的排序,无间隙
  3. 每个类或接口都单独占据一个class文件

javap -verbose TestClass查看字节码内容

Java class文件结构(规范篇)

Java class文件结构(实例篇)

class文件弊端:

  1. 内存占用大,不适合移动端
  2. 堆栈的加载模式,加载速度慢
  3. 文件IO操作多,类查找慢

dex文件


能够被DVM识别,加载并执行的文件格式,记录整个工程中所有类文件的信息。

如何生成一个dex文件:

  1. 通过IDE自动帮我们build生成
  2. 手动通过dx命名去生成dex文件

如何运行dex文件:

//javac -target 1.6 -source 1.6 Hello.java
//dx --dex --output Hell.dex Hello.class
//adb push Hello.dex /storage/emulated/0
//adb shell进入手机控制台
//dalvikvm -cp /sdcard/Hello.dex Hello

dex文件格式详解

  1. 一种8位字节的二进制流文件
  2. 各个流按顺序紧密的排列
  3. 整个应用中所有Java源文件都放在一个dex中

一篇文章带你搞懂DEX文件的结构

dex和class文件的区别


  1. dvm执行的是.dex格式文件 jvm执行的是.class文件 android程序编译完之后生产.class文件,然后,dex工具会把.class文件处理成.dex文件,然后把资源文件和.dex文件等打包成.apk文件。apk就是android package的意思。 jvm执行的是.class文件。
  2. .class文件存在很多的冗余信息,dex工具会去除冗余信息,并把所有的.class文件整合到.dex文件中。减少了I/O操作,提高了类的查找速度
  3. dvm是基于寄存器的虚拟机 而jvm执行是基于虚拟栈的虚拟机。寄存器存取速度比栈快的多,dvm可以根据硬件实现最大的优化,比较适合移动设备。

JVM内存结构


虚拟机相关知识--面试专用_第1张图片
四大模块需要掌握的: 1.内存空间划分 2.内存管理 3.gc 4.classloader

内存空间划分

程序计数器(Program Counter Register)

一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空。

Java虚拟机栈(Java Virtual Machine Stacks)

与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

Java堆(Java Heap)

对大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

GC要回收的部分

方法区(Method Area)

与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是JVM规范中定义的一个概念,具体放在哪里,不同的实现可以放在不同的地方。

运行时常量(Runtime Constant Pool)

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

JVM----新生代,老年代,永久代

内存管理


什么时候触发垃圾回收?

  1. java虚拟机无法再为新的对象分配内存空间了
  2. 手动调用System.gc()方法(强烈不推荐)

手动调用,加大虚拟机压力
低优先级的GC线程,被运行时就会执行GC

怎么判断对象是否已经“死去”?

常见的判定方法有两种:引用计数法和可达性分析算法,HotSpot中采用的是可达性分析算法。

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

客观地说,引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法,但是主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。

可达性分析算法

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如下图所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。

虚拟机相关知识--面试专用_第2张图片
引用记数算法 jdk1.2之前使用
可达性算法 jdk1.2之后使用

垃圾收集有哪些算法,各自的特点?

标记 - 清除算法

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

虚拟机相关知识--面试专用_第3张图片

复制算法

为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。

虚拟机相关知识--面试专用_第4张图片

标记 - 整理算法

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

虚拟机相关知识--面试专用_第5张图片
更多:

1.Java虚拟机面试题精选(一)

2.Java虚拟机面试题精选(二)

Dalvik和JVM不同


  1. 执行的文件不同,一个是class,一个是dex
  2. 类加载的系统与JVM区别较大
  3. 可以同时存在多个DVM
  4. Dalvik是基于寄存器,而JVM是基于栈的

Dalvik和ART的区别


  1. DVM使用JIT来将字节码转换成机器码,效率低
  2. ART采用了AOT预编译技术,执行速度更快
  3. ART会占用更多的应用安装时间和存储空间

AOT安装时候就会将字节码转化为机器码,不需要每次运行时候转化,执行速度更快
但是所需要空间更大

四大引用


强引用软引用弱引用虚引用,最常用的就是强引用弱引用

**强引用:**在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
**弱引用:**用来描述一些还有用但并非必需的对象,使用SoftReference类来实现软引用,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
**弱引用:**用来描述非必需对象的,使用WeakReference类来实现弱引用,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
**虚引用:**是最弱的一种引用关系,使用PhantomReference类来实现虚引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

java代码编译过程


Java代码编译过程简述

Java代码编译和执行的整个过程

class字节码加载过程


类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中验证、准备、解析3个部分统称为连接。

加载:
“类加载”过程的一个阶段,在加载阶段,虚拟机需要完成以下3件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证:
连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。从整体上看,验证阶段大致上会完成下面4个阶段的检验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。

准备:
该阶段是正式为类变量(static修饰的变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这里所说的初始值“通常情况”下是数据类型的零值,下表列出了Java中所有基本数据类型的零值。
虚拟机相关知识--面试专用_第6张图片
解析:

该阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化:

初始化阶段是执行类构造器()方法的过程。()方法是由编译器自动收集类中的所有类变量(static修饰的变量)的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。如果该类存在父类,则虚拟机会保证在执行子类的()方法前,父类的()方法已经执行完毕。因此在虚拟机中第一个被执行()方法的类肯定是java.lang.Object

类加载器


java常用类加载器

虚拟机相关知识--面试专用_第7张图片
从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader

从Java开发人员的角度来看,绝大部分Java程序都会使用到以下3种系统提供的类加载器。

启动类加载器(Bootstrap ClassLoader):
这个类加载器负责将存放在\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。

扩展类加载器(Extension ClassLoader):
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器(Application ClassLoader):
这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

一般都是由这3种类加载器相关配合进行加载,如果有必要,还可以加入自己定义的类加载器.

双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

双亲委派模型好处

使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java 类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。

类加载机制系列1——深入理解Java中的类加载器

Android中的ClassLoader

BootClassLoader加载Framework class文件
PathClassLoader加载已经安装到系统APK的class文件
DexClassLoader加载指定目录中的class字节码文件
BaseDexClassLoader前面两个的父类

//获取应用中需要加载的classLoader
ClassLoader classLoader = getClassLoader();
if(classLoader != null){
     
    Log.e("TAG", "classLoader:" + classLoader.toString());

    while (classLoader.getParent() != null){
     
        classLoader = classLoader.getParent();
        Log.e("TAG", "classLoader:" + classLoader.toString());
    }
}

类加载机制系列2——深入理解Android中的类加载器

参考


1.Java虚拟机面试题精选(一)

2.Java虚拟机面试题精选(二)

3.JVM(七):JVM内存结构

JVM

4.Java中new一个对象是一个怎样的过程?JVM中发生了什么?

5.Java程序员不可不知的对象创建底层步骤细节—很重要很关键

6.Java面试相关(一)-- Java类加载全过程

7.Android中类的加载以及应用

8.类加载机制系列2——深入理解Android中的类加载器

9.类加载机制系列1——深入理解Java中的类加载器

10.JVM----新生代,老年代,永久代

你可能感兴趣的:(#,java面试基础)