# Java相关/Java虚拟机面试题.md

## 1、JVM的内存模型的理解，threadlocal使用场景及注意事项？

<http://www.iteye.com/topic/806990> <http://ifeve.com/java-memory-model-0/>

## 2、描述一下GC的原理和回收策略？

提到垃圾回收，我们可以先思考一下，如果我们去做垃圾回收需要解决哪些问题？ 🤔

一般说来，我们要解决一些三个问题：

哪些内存回收？ 什么时候回收？ 如何回收？ 这些问题分别对应着引用管理和回收策略等方案。

提到引用，我们都知道Java中有四种引用类型：

强引用：代码中普遍存在的，只要强引用还存在，垃圾收集器就不会回收掉被引用的对象。 软引用：SoftReference，用来描述还有用但是非必须的对象，当内存不足的时候回回收这类对象。 弱引用：WeakReference，用来描述非必须对象，弱引用的对象只能生存到下一次GC发生时，当GC发生时，无论内存是否足够，都会回收该对象。 虚引用：PhantomReference，一个对象是否有虚引用的存在，完全不会对其生存时间产生影响，也无法通过虚引用取得一个对象的引用，它存在的唯一目的是在这个对象被回收时可以收到一个系统通知。 不同的引用类型，在做GC时会区别对待，我们平时生成的Java对象，默认都是强引用，也就是说只要强引用还在，GC就不会回收，那么如何判断强引用是否存在呢？🤔

一个简单的思路就是：引用计数法，有对这个对象的引用就+1，不再引用就-1，但是这种方式看起来简单美好，但它却不嫩解决循环引用计数的问题。

因此可达性分析算法登上历史舞台😎，用它来判断对象的引用是否存在。

可达性分析算法通过一系列称为GCRoots的对象作为起始点，从这些节点从上向下搜索，搜走过的路径称为引用链，当一个对象没有任何引用链 与GCRoots连接时就说明此对象不可用，也就是对象不可达。

GC Roots对象通常包括：

虚拟机栈中引用的对象（栈帧中的本地变量表） 方法去中类的静态属性引用的对象 方法区中常量引用的对象 Native方法引用的对象 可达性分析算法整个流程如下所示：

第一次标记：对象在经过可达性分析后发现没有与GC Roots有引用链，则进行第一次标记并进行一次筛选，筛选条件是：该对象是否有必要执行finalize()方法。没有覆盖finalize()方法或者finalize()方法已经被执行过都会被 认为没有必要执行。 如果有必要执行：则该对象会被放在一个F-Queue队列，并稍后在由虚拟机建立的低优先级Finalizer线程中触发该对象的finalize()方法，但不保证一定等待它执行结束，因为如果这个对象的finalize()方法发生了死循环或者执行 时间较长的情况，会阻塞F-Queue队列里的其他对象，影响GC。 第二次标记：GC对F-Queue队列里的对象进行第二次标记，如果在第二次标记时该对象又成功被引用，则会被移除即将回收的集合，否则会被回收。

## 3、跟Art、Dalvik对比

## 4、JVM基础，GC机制。

JVM基本构成![image](https://user-gold-cdn.xitu.io/2017/11/21/15fdc4cfaf97ca63?imageslim)

从上图可知，JVM主要包括四个部分：

1.类加载器（ClassLoader）:在JVM启动时或者在类运行将需要的class加载到JVM中。（下图表示了从java源文件到JVM的整个过程，可配合理解。

![image](https://user-gold-cdn.xitu.io/2017/11/21/15fdc4cfb0e97353?imageslimo)

2.执行引擎：负责执行class文件中包含的字节码指令；

3.内存区（也叫运行时数据区）：是在JVM运行的时候操作所分配的内存区。运行时内存区主要可以划分为5个区域，如图：

![image](https://user-gold-cdn.xitu.io/2017/11/21/15fdc4cfaf3c52a9?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)

方法区(MethodArea)：用于存储类结构信息的地方，包括常量池、静态量、构造函数等。虽然JVM规范把方法区描述为堆的一个辑部分， 但它却有个别名non-heap（非堆），所以大家要搞混淆了。方法区还包含一个运行时常量池。

java堆(Heap)：存储java实例或者对象的地方。这块是G的主要区域。从存储的内容我们可以很容易知道，方法和堆是被所有java线程共享的。

java栈(Stack)：java栈总是和线程关联在一起，每当创一个线程时，JVM就会为这个线程创建一个对应的java栈在这个java栈中又会包含多个栈帧，每运行一个方法就建一个栈帧，用于存储局部变量表、操作栈、方法返回等。每一个方法从调用直至执行完成的过程，就对应一栈帧在java栈中入栈到出栈的过程。所以java栈是现成有的。

程序计数器(PCRegister)：用于保存当前线程执行的内存地址。由于JV程序是多线程执行的（线程轮流切换），所以为了保证程切换回来后，还能恢复到原先状态，就需要一个独立计数器，记录之前中断的地方，可见程序计数器也是线私有的。

本地方法栈(Native MethodStack)：和java栈的作用差不多，只不过是为JVM使用到native方法服务的。

4.本地方法接口：主要是调用C或C++实现的本地方法及回结果。

GC机制

垃圾收集器一般必须完成两件事：

* 检测出垃圾；
* 回收垃圾。

怎么检测出垃圾？

一般有以下几种方法：

引用计数法：

给一个对象添加引用计数器，每当有个地方引用它，计器就加1；引用失效就减1。好了，问题来了，如果我有个对象A和B，互相引用，除此之外，没有其他任何对象用它们，实际上这两个对象已经无法访问，即是我们说垃圾对象。但是互相引用，计数不为0，导致无法回收，以还有另一种方法：

可达性分析算法：

以根集对象为起始点进行搜索，如果有对象不可达的话即是垃圾对象。这里的根集一般包括java栈中引用的对、方法区常良池中引用的对象、本地方法中引用的对象等。

总之，JVM在做垃圾回收的时候，会检查堆中的所有对象否会被这些根集对象引用，不能够被引用的对象就会被圾收集器回收。一般回收算法也有如下几种：

1\).标记-清除（Mark-sweep）

2\).复制（Copying

3\).标记-整理（Mark-Compact）

4\).分代收集算法

## 5、类的加载器，双亲机制，Android的类加载器。

类的加载器

大家都知道，当我们写好一个Java程序之后，不是管是C还是BS应用，都是由若干个.class文件组织而成的一个完整的Java应用程序，当程序在运行时，即会调用该程序的一个入口函数来调用系统的相关功能，而这些功能都被封装在不同的class文件当中，所以经常要从这个class文件中要调用另外一个class文件中的方法，如果另外一个文件不存在的，则会引发系统异常。

而程序在启动的时候，并不会一次性加载程序所要用的有class文件，而是根据程序的需要，通过Java的类加载制（ClassLoader）来动态加载某个class文件到内存当的，从而只有class文件被载入到了内存之后，才能被其class所引用。所以ClassLoader就是用来动态加载class件到内存当中用的。

双亲机制

1、原理介绍

ClassLoader使用的是双亲委托模型来搜索类的，每个ClasLoader实例都有一个父类加载器的引用（不是继承的关，是一个包含的关系），虚拟机内置的类加载器（Bootstap ClassLoader）本身没有父类加载器，但可以用作其它lassLoader实例的的父类加载器。

当一个ClassLoader实例需要加载某个类时，它会试图亲搜索某个类之前，先把这个任务委托给它的父类加载器这个过程是由上至下依次检查的，首先由最顶层的类加载器Bootstrap ClassLoader试图加载，如果没加载到，则把任务转交给Extension ClassLoader试图加载，如果也没加载到，则转交给App ClassLoader 进行加载，如果它也没有加载得到的话，则返回给委托的发起者，由它到指定的文件系统或网络等URL中加载该类。

如果它们都没有加载到这个类时，则抛出ClassNotFoundEception异常。否则将这个找到的类生成一个类的定义，将它加载到内存当中，最后返回这个类在内存中的Class例对象。

2、为什么要使用双亲委托这种模型呢？

因为这样可以避免重复加载，当父亲已经加载了该类的候，就没有必要子ClassLoader再加载一次。

考虑到安全因素，我们试想一下，如果不使用这种委托式，那我们就可以随时使用自定义的String来动态替代jva核心api中定义的类型，这样会存在非常大的安全隐患而双亲委托的方式，就可以避免这种情况，因为String经在启动时就被引导类加载器（BootstrcpClassLoader）加载，所以用户自定义的ClassLoader永也无法加载一个自己写的String，除非你改变JDK中Classoader搜索类的默认算法。

3、但是JVM在搜索类的时候，又是如何判定两个class是相同的呢？

JVM在判定两个class是否相同时，不仅要判断两个类名否相同，而且要判断是否由同一个类加载器实例加载的。

只有两者同时满足的情况下，JVM才认为这两个class是同的。就算两个class是同一份class字节码，如果被两不同的ClassLoader实例所加载，JVM也会认为它们是两个不同class。

比如网络上的一个Java类org.classloader.simple.NetClssLoaderSimple，javac编译之后生成字节码文件NetClasLoaderSimple.class，ClassLoaderA和ClassLoaderB这个类加载器并读取了NetClassLoaderSimple.class文件并分别定义出了java.lang.Class实例来表示这个类，对JVM来说，它们是两个不同的实例对象，但它们确实是一份字节码文件，如果试图将这个Class实例生成具体的象进行转换时，就会抛运行时异常java.lang.ClassCaseEception，提示这是两个不同的类型。

Android类加载器

对于Android而言，最终的apk文件包含的是dex类型的文，dex文件是将class文件重新打包，打包的规则又不是单地压缩，而是完全对class文件内部的各种函数表，变表进行优化，产生一个新的文件，即dex文件。因此加载种特殊的Class文件就需要特殊的类加载器DexClassLoade。

可以动态加载Jar通过URLClassLoader

1.ClassLoader 隔离问题JVM识别一个类是由：ClassLoaderid+PackageName+ClassName。

2.加载不同Jar包中的公共类：

* 让父ClassLoader加载公共的Jar，子ClassLoade载包含公共Jar的Jar，此时子ClassLoader在加载Jar的时候会先去父ClassLoader中找。(只适用Java)
* 重写加载包含公共Jar的Jar的ClassLoader，在loClass中找到已经加载过公共Jar的ClassLoader，是把父ClassLoader替换掉。(只适用Java)
* 在生成包含公共Jar的Jar时候把公共Jar去掉。

## 6、JVM的类加载机制是什么？有哪些实现方式？

类的加载就是虚拟机通过一个类的全限定名来获取描述此类的二进制字节流，而完成这个加载动作的就是类加载器。

类和类加载器息息相关，判定两个类是否相等，只有在这两个类被同一个类加载器加载的情况下才有意义，否则即便是两个类来自同一个Class文件，被不同类加载器加载，它们也是不相等的。

注：这里的相等性保函Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果以及Instance关键字对对象所属关系的判定结果等。

类加载器可以分为三类：

* 启动类加载器（Bootstrap ClassLoader）：负责加载\lib目录下或者被-Xbootclasspath参数所指定的路径的，并且是被虚拟机所识别的库到内存中。
* 扩展类加载器（Extension ClassLoader）：负责加载\lib\ext目录下或者被java.ext.dirs系统变量所指定的路径的所有类库到内存中。
* 应用类加载器（Application ClassLoader）：负责加载用户类路径上的指定类库，如果应用程序中没有实现自己的类加载器，一般就是这个类加载器去加载应用程序中的类库。 这么多类加载器，那么当类在加载的时候会使用哪个加载器呢？

这个时候就要提到类加载器的双亲委派模型，流程图如下所示：

![image](https://github.com/guoxiaoxing/android-open-source-project-analysis/raw/master/art/native/vm/classloader_model_structure.png)

双亲委派模型的整个工作流程非常的简单，如下所示：

如果一个类加载器收到了加载类的请求，它不会自己立去加载类，它会先去请求父类加载器，每个层次的类加器都是如此。层层传递，直到传递到最高层的类加载器只有当 父类加载器反馈自己无法加载这个类，才会有当子类加载器去加载该类。

关于双亲委派机制，在ClassLoader源码里也可以看出，如下所示：

```
public abstract class ClassLoader {

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            //首先，检查该类是否已经被加载
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //先调用父类加载器去加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    //如果父类加载器没有加载到该类，则自己去执行加载
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }
}
```

为什么要这么做呢？

这是为了要让越基础的类由越高层的类加载器加载，例如Object类，无论哪个类加载器去尝试加载这个类，最终都会传递给最高层的类加载器去加载，前面我们也说过，类的相等性是由 类与其类加载器共同判定的，这样Object类无论在何种类加载器环境下都是同一个类。

相反如果没有双亲委派模型，那么每个类加载器都会去加载Object，那么系统中就会出现多个不同的Object类了，如此一来系统的最基础的行为也就无法保证了。

类加载机制：

类的加载指的是将类的.class文件中的二进制数据读入到内存中，将其放在运行时数据区的方法去内，然后在堆区创建一个java.lang.Class对象，用来封装在方法区内的数据结构。类的加载最终是在堆区内的Class对象，Class对象封装了类在方法区内的数据结构，并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载有三种方式：

1）命令行启动应用时候由JVM初始化加载

2）通过Class.forName（）方法动态加载

3）通过ClassLoader.loadClass（）方法动态加载

## 7、JVM的常见垃圾回收算法？

1）标记-清除算法：前后线标记处所有需要回收的对象，在标记完成后统一回收有被标记的对象。

2）复制算法：将可用内存按容量划分为大小相等的两块，每次只使用其中的一块。当一块内存用完了，将其存在另外一块上面，然后再把已使用过的内存空间一次清理掉。

3）标记-整理算法：标记过程与“标记-清除”算法一样，但后续步骤不是直接对可回收对象进行清理，而是让其一端移动，然后直接清理掉端边界以外的内存。

4）分代收集算法：一般是把Java堆分为新生代和老年代，根据各个年代的特点采用最适当的收集算法。新生代都发现有大批对象死去，选用复制算法。老年代中因为对象存活率高，必须使用“标记-清理”或“标记-整理”算法来进行回收。

## 8、JVM调优的常见命令行工具有哪些？JVM常见的调优参数有哪些？

（1）JVM调优的常见命令工具包括：

1）jps命令用于查询正在运行的JVM进程，

2）jstat可以实时显示本地或远程JVM进程中类装载、内存、垃圾收集、JIT编译等数据

3）jinfo用于查询当前运行这的JVM属性和参数的值。

4）jmap用于显示当前Java堆和永久代的详细信息

5）jhat用于分析使用jmap生成的dump文件，是JDK自带的工具

6）jstack用于生成当前JVM的所有线程快照，线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的原因。

（2）JVM常见的调优参数包括：

-Xmx

指定java程序的最大堆内存, 使用java -Xmx5000M -version判断当前系统能分配的最大堆内存

-Xms

指定最小堆内存, 通常设置成跟最大堆内存一样，减少GC

-Xmn

设置年轻代大小。整个堆大小=年轻代大小 + 年老代大小。所以增大年轻代后，将会减小年老代大小。此值对系统性能影响较大，Sun官方推荐配置为整个堆的3/8。

-Xss

指定线程的最大栈空间, 此参数决定了java函数调用的深度, 值越大调用深度越深, 若值太小则容易出栈溢出错误(StackOverflowError)

-XX:PermSize

指定方法区(永久区)的初始值,默认是物理内存的1/64， 在Java8永久区移除, 代之的是元数据区， 由-XX:MetaspaceSize指定

-XX:MaxPermSize

指定方法区的最大值, 默认是物理内存的1/4， 在java8中由-XX:MaxMetaspaceSize指定元数据区的大小

-XX:NewRatio=n

年老代与年轻代的比值，-XX:NewRatio=2, 表示年老代与年轻代的比值为2:1

-XX:SurvivorRatio=n

Eden区与Survivor区的大小比值，-XX:SurvivorRatio=8表示Eden区与Survivor区的大小比值是8:1:1，因为Survivor区有两个(from, to)

## 9、GC收集器简介？以及它的内存划分怎么样的？

（1）简介：

Garbage-First（G1，垃圾优先）收集器是服务类型的收集器，目标是多处理器机器、大内存机器。它高度符合垃圾收集暂停时间的目标，同时实现高吞吐量。Oracle JDK 7 update 4 以及更新发布版完全支持G1垃圾收集器

（2）G1的内存划分方式：

它是将堆内存被划分为多个大小相等的 heap 区,每个heap区都是逻辑上连续的一段内存(virtual memory). 其中一部分区域被当成老一代收集器相同的角色(eden, survivor, old), 但每个角色的区域个数都不是固定的。这在内存使用上提供了更多的灵活性

## 10、Java的虚拟机JVM的两个内存：栈内存和堆内存的区别是什么？

Java把内存划分成两种：一种是栈内存，一种是堆内存。两者的区别是：

1）栈内存：在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。 当在一段代码块定义一个变量时，Java就在栈中为这个变量分配内存空间，当超过变量的作用域后，Java会自动释放掉为该变量所分配的内存空间，该内存空间可以立即被另作他用。

2）堆内存：堆内存用来存放由new创建的对象和数组。在堆中分配的内存，由Java虚拟机的自动垃圾回收器来管理。

## 11、jstack,jmap,jutil分别的意义？如何线上排查JVM的相关问题？

## 12、Java虚拟机中，数据类型可以分为哪几类？

## 13、讲一讲垃圾回收算法。

## 14、如何解决内存碎片的问题？

## 15、如何解决同时存在的对象创建和对象回收问题？

## 16、讲一讲内存分代及生命周期。

## 17、什么情况下触发垃圾回收？

## 18、如何选择合适的垃圾收集算法？

## 19、JVM中最大堆大小有没有限制？

## 20、JVM方法区存储内容 是否会动态扩展 是否会出现内存溢出 出现的原因有哪些。

## 21、JVM老年代和新生代的比例？

## 22、堆大小通过什么参数设置？

## 23、JVM有哪三种垃圾回收器？

## 24、吞吐量优先选择什么垃圾回收器？响应时间优先呢？

## 25、如何进行JVM调优？有哪些方法？

## 26、JVM的引用树，什么变量能作为GCRoot？

## 27、JVM 内存区域 开线程影响哪块内存

## 28、哪些情况下的对象会被垃圾回收机制处理掉？

Java 垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代，对象根据其存活的时间被保存在对应世代的区域中。一般的实现是划分成3个世代：年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候，它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说，一个应用中的大部分对象的存活时间都很短。比如局部变量的存活时间就只在方法的执行过程中。基于这一点，对于年轻世代的垃圾回收算法就可以很有针对性。

## 29、Java的垃圾回收机制，引用计数法两个对象互相引用如何解决？

## 30、Java运行时数据区域，导致内存溢出的原因。

## 31、对象创建、内存布局，访问定位等。

## 32、JVM方法区存储内容 是否会动态扩展 是否会出现内存溢出 出现的原因有哪些。

## 33、如何理解Java的虚函数表？


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://adark0915.gitbook.io/android/java-xiang-guan/java-xu-ni-ji-mian-shi-ti.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
