1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
Java虚拟机是一个可执行的Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。Java被设计成允许应用程序可以运行在任何平台,而不需要程序员为每一个平套单独编写或重新编译。Java虚拟机让这个变成可能,因为它知道底层硬件平台的指令长度和其他特性。
2.Java内存结构?
Java内存结构图:
方法区和堆是所有线程共享的内存区域,而Java栈,本地方法栈和程序员计数器是运行时线程私有的内存区域
- Java堆(Heap):Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存
- 方法区(Method Area):方法区与Java堆一样,是各个线程的共享区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 程序计数器(Program Counter Register):程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器
- JVM栈(JVM Stacks):栈与程序计数器一样,Java虚拟机也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
- 本地方法栈(Native Method Stacks):本地方法栈与虚拟机栈所发挥的作用是非常相似,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是虚拟机使用到的native方法服务。
3.解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法
通常我们定义一个基本的数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。
String str = new String("hello");
上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量是放在方法区的。
补充1:较新版本的Java(从Java 6的某个更新开始)中,由于JIT编译器的发展和”逃逸分析”技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一定分配在堆上这件事情已经变得不那么绝对了。
补充2:运行时常量池相当于Class文件常量池具有动态性,Java语言并不要求常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String类的intern()方法就是这样的。 看看下面代码的执行结果是什么并且比较一下Java 7以前和以后的运行结果是否一致。
String s1 = new StringBuilder("go")
.append("od").toString();
System.out.println(s1.intern() == s1);
String s2 = new StringBuilder("ja")
.append("va").toString();
System.out.println(s2.intern() == s2);
4.对象的创建过程是怎样的?
- JVM会先去方法区找到有没有所创建对象的类存在,有就可以创建对象,没有则把该类加载到方法区
- 创建类的对象时,首先会去堆内存分配空间
- 当空间分配完后,加载对象中所有非静态成员变量到该空间下
- 所有非静态成员变量加载完成后,对所有非静态成员进行默认初始化
- 所有非静态成员默认初始化完成后,调用相应的构造方法到栈中
- 在栈中执行构造函数时,先执行隐式,在执行构造方法中书写的代码
- 执行顺序:静态代码库,构造代码块,构造方法
- 当整个构造方法全部执行完,此对象创建完成,并把堆内存中的分配的空间地址赋给对象名
5.方法区内存溢出怎么处理?
- 检查代码中是否出现死循环
- 是否创建过多或过大的对象
- 是否出现死锁现象
- 提高JVM堆内存大小的配置
6.你知道哪些垃圾回收算法?
GC最基础的算法有三种:标记-清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般采用分代收集算法
- 标记-清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
- 复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
- 标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
- 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
7.垃圾回收器
- Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。
- ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
- Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。
- Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
- CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
- G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征
8.如何判断一个对象是否应该被回收
- 引用计数法:每一个对象有一个引用技术属性,新增一个引用时技术加1,引用释放时计数减1,计数为0时可以回收。
- 可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。
9.JVM的永久代会发生垃圾回收吗?
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
10.引用的分类
- 强引用:GC时不会被回收
- 软引用:描述有用但不是必须的对象,在发生内存溢出已成之前被回收
- 弱引用:描述有用但不是必须的对象,在下一次GC时被回收
- 虚引用:无法通过虚引用获得对象,用PhantomReference实现虚引用,虚引用用来在GC时返回一个通知。
11.什么时类加载
类的加载指的是将类的.class文件中的二进制数据读入内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.class对象,用来封装类在方法区内的数据结构。类的加载最终产品位于堆区的Class对象,Class对象封装了类在方法区的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口。
类加载器如图:
- 启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库
- 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器
- 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器
双亲委派机制:类加载器收到类加载请求,自己不加载,向上委托给父类加载,父类加载不了,再自己加载。优势就是避免Java核心API篡改
12.类的生命周期
类的生命周期包括几个部分:加载、连接、初始化、使用、卸载,其中前三部是类的加载的过程,如图:
- 加载:查找并加载类的二进制数据,在Java堆中也创建一个Java.lang.Class类的对象
- 连接:连接又包含三块内容:验证、准备、初始化。 1)验证,文件格式、元数据、字节码、符号引用验证; 2)准备,为类的静态变量分配内存,并将其初始化为默认值; 3)解析,把类中的符号引用转换为直接引用
- 初始化:为类的静态变量赋予正确的初始值
- 使用:new出对象程序中使用
- 卸载:执行垃圾回收
13.有哪些垃圾回收器?
- Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。
- ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
- Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。
- Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
- CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
- G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征