SIXTEEN------JVM

163 阅读16分钟

1、说一说JVM的概念

JVM即 Java Virtual Machine ,Java虚拟机。是用于运行Java字节码的虚拟机。
Java虚拟机主要由一套字节码指令集、一组程序寄存器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。
JVM是运行在操作系统之上,不与硬件设备直接交互。

2、说一下 JVM 的主要组成部分?及其各自作用

类加载器子系统(Class Loader SubSystem)

运行时数据区(Runtime Data Area)

执行引擎(Execution Engine)

本地接口库(Native Interface

• 类加载器子系统用于将编译好的字节码文件(.Class文件)加载到JVM中;

• 运行时数据区用于存储在JVM运行过程中产生的数据,
  包括程序计数器、方法区、本地方法区、虚拟机栈和虚拟机堆;

• 执行引擎包括即时编译器和垃圾回收器,即时编译器用于将Java字节码编译成具体的机器码,
  垃圾回收器用于回收在运行过程中不再使用的对象;

• 本地接口库用于调用操作系统的本地方法库完成具体的指令操作。

3、 说一下堆栈的区别?

• 栈内存存储的是局部变量(定义在方法中的都是局部变量);而堆内存存储的是实体对象,凡是new建立的都是在堆中

• 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短

• 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体对象会被垃圾回收机制不定时回收。

4、请介绍类加载过程?(描述一下 JVM 加载 Class 文件的原理机制?)

一个Java文件从编码完成到最终执行,一般主要包括两个过程:编译、运行

编译:即把我们写好的java文件,通过javac命令编译成字节码,也就是我们常说的.class文件。

运行:则是把编译好的.class文件交给Java虚拟机(JVM)执行。

而我们所说的类加载过程即是指JVM虚拟机把.Class文件中类信息加载进内存,并进行解析生成对应的Class对象的过程。

类加载的过程主要分为三个部分:

加载

连接

初始化

而连接又可以细分为三个小部分:

验证
准备
解析


• 加载:

class字节码文件通过类加载器装载入内存中。即JVM读取Class文件,
并根据Class文件在堆中创建java.lang.Class对象

• 验证:

确保Class文件符合虚拟机的规范,不会造成安全错误,只有通过验证的Class文件才能被JVM加载。

• 准备:

主要在方法区中为类变量(不是实例变量)分配内存空间,并设置类中变量的初始值。 

特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。
比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,
final static tmp = 456, 那么	该阶段tmp的初值就是456

• 解析:

虚拟机将常量池中的符号引用替换成直接引用的过程。

符号(字符串)引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,
一个类的相关信息。
直接引用:可以理解为一个内存地址,或者一个偏移量
例如,调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

• 初始化:

主要是对类变量初始化,是执行类构造器的过程,通过执行类构造器的<Client>方法为类进行初始化。
(如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。)

5、类加载器有哪些?

JVM提供了3种类加载器

启动类加载器(BootStrap ClassLoader):负责加载Java_Home/lib目录中的类库

扩展类加载器:负责加载Java_Home/lib/ext目录中的类库

应用程序类加载器:负责加载用户路径(classpath)上的类库  


6、 什么是双亲委派模型?

如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class)时,子类加载器才会尝试自己去加载类。
双亲委派模型的好处:
双亲委派机制能保证多加载器加载某个类时,最终都是由一个加载器加载,可以避免类的重复加载(相同的类文件被不同的类加载器加载产生的是两个不同的类)
也避免了java的核心API被篡改。

7、在JVM后台运行的线程有哪些?

虚拟机线程(JVM Thread)  
GC线程(Gabage Collection)  
编译器线程  
周期性任务线程  
信号分发线程

8、Java 内存(区域)分配问题(一个完整的Java程序运行过程会涉及以下内存区域:)

Java 程序运行时,需要在内存中分配空间。为了提高运算效率,就对空间进行了不同区域的划分,因为每一片内存区域都有特定的处理数据方式和内存管理方式。
JVM的内存区域分为线程私有区域(程序计数器、虚拟机栈、本地方法区),线程共享区域(堆、方法区)和直接内存

线程私有区域的生命周期与线程相同,随着线程的启动而创建,随着线程结束而销毁。
线程共享区域随着虚拟机的启动而创建,随虚拟机的关闭而销毁。
直接内存也叫堆外内存,它并不是JVM运行时数据区的一部分,但在并发编程中被频繁使用。(JDK的NIO模块提供的I/O操作方式就是基于堆外内存实现的)

程序计数器(寄存器):
栈:保存局部变量
本地方法区:和栈作用类似,区别是虚拟机栈为执行Java方法服务,本地方法区为Native(本地)方法服务

方法区:即常说的永久代, 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
堆:用来保存动态产生的数据,比如new出来的对象。

9、GC 是什么? 为什么要有 GC?

GC(Gabage Collection)是垃圾回收的意思,垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
要请求垃圾收集,可以调用下面的方法之一:System.gc()Runtime.getRuntime().gc(),但JVM可以屏蔽掉显示的垃圾回收调用。
10、怎么判断对象是否可以被回收?(或者 GC 对象的判定方法)

一般有两种方法来判断:

• 引用计数法:

为每个对象创建一个引用计数,有对象引用时计数 +1,引用被释放时计数 -1,
当计数器为 0 时表示该对象没有被引用就可以被回收。
它有一个缺点容易引起循环引用的问题(两个对象互相引用,导致引用一致存在而不能被回收)

• 可达性分析:

通过根搜索算法 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。
当一个对象到 GC Roots 没有任何引用链相连时,说明其已经死亡,则证明此对象是可以被回收的。

11、说一下 JVM 有哪些垃圾回收算法(机制)?

常用的垃圾回收算法有如下四种:标记-清除、复制、标记-整理和分代收集。
• 标记-清除

首先标记出所有需要被回收的对象,然后在标记完成后统一回收掉所有被标记的对象。
这个算法简单但是有两个缺点:一是标记和清除的效率不是很高;二是标记和清除后会产生很多的内存碎片,
导致可用的内存空间不连续。

• 复制

这个算法将可用的内存空间分为大小相等的两块,每次只是用其中的一块,当这一块被用完的时候,
就将还存活的对象复制到另一块中,然后把原已使用过的那一块内存空间一次回收掉。这个算法常用于新生代的垃圾回收。
复制算法的内存清理效率高且易于实现,但同一时刻只能有一个内存区域可用,因此造成大量内存浪费。

• 标记-整理

这个算法结合了标记清除算法和复制算法的优点,标记出所有需要被回收的对象,把所有存活的对象移到内存的另一端,
把所有存活对象边界以外的内存空间都回收掉。

• 分代收集

根据对象生存周期的不同将内存空间划分为不同的块,然后对不同的块使用不同的回收算法,称为分代收集算法。
一般把Java堆分为新生代和老年代,新生代中对象的存活周期短,只有少量存活的对象,所以可以使用复制算法,
而老年代中对象存活时间长,而且对象比较多,所以可以采用标记-清除和标记-整理算法。

12、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。
通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就会回收这些内存空间。

程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。 gc只回收heap里的对象,对象都是一样的,只要没有对它的引用,就可以被回收(但是不一定被回收,对象的回收和是否static没有什么关系!)

不要频繁使用gc函数,保持代码健壮(记得将不用的变量置为null),让虚拟机去管理内存。

13、Java 中会存在内存泄漏吗? 请简单描述

所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。
理论上来说,Java是有GC垃圾回收机制的,但是即使这样,Java也还是存在着内存泄漏的情况。

• 长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露

通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,
即这个对象无用但是却无法被垃圾回收器回收的,这就是Java中可能出现内存泄露的情况,
例如缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,
这个对象一直被缓存引用,但却不再被使用。

• 当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。

14、System.gc() 和 Runtime.gc() 会做什么事情?

调用两个方法都是通知让 JVM 进行垃圾回收,但是系统什么时候回收,不确定,只能理论上尽快回收。

15、 Java 中都有哪些引用类型?

在Java中一切皆对象,对象的操作是通过该对象的引用(Reference)实现的,Java中引用类型有4种:

• 强引用:在Java中最常见的就是强引用,在把一个对象赋给一个引用变量时,这个引用变量就是一个强引用。
		  有强引用的对象一定是可达性状态,所以不会被垃圾回收机制回收,
          因此强引用是造成Java内存泄漏的主要原因。

• 软引用:软引用通过SoftReference类实现的,如果一个对象只有软引用,则在系统内存空间不足时该对象被回收。

• 弱引用:弱引用通过WeakReference类实现的,如果一个对象只有弱引用,则在垃圾回收过程中一定会被回收。

• 虚引用:虚引用通过PhantomReference类实现的[ˈfæntəm],主要跟踪对象的垃圾回收状态。

16、说一说JVM运行时内存

JVM运行时内存也叫JVM堆,从GC角度可以将JVM堆分为新生代、老年代、永久代。其中新生代默认占1/3堆空间,老年代默认占2/3堆空间,永久代占非常少的堆空间。

• 新生代:JVM新创建的对象会被存放在新生代,由于JVM会频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。

• 老年代:老年代主要存放生命周期长的对象和大对象,老年代的GC过程叫做MajorGC。
		 在老年代,对象比较稳定,MajorGC不会被频繁触发。
         
• 永久代:主要存放Class和Meta([ˈmitə]元数据)的信息。永久代中GC不会在程序运行期间对永久代的内存进行清理,
		 导致永久代的内存随着加载的Class文件增加而增加,在加载的Class文件过多时会抛出Out Of Memory异常
         (比如Tomcat引用jar文件过多时导致JVM内存不足而无法启动)
         在Java8中永久代被元数据区(也叫元空间)取代。二者作用类似,区别在于元数据区并没有使用虚拟机的内存
         而直接使用操作系统的内存。因此元空间的大小不受JVM内存的限制,只和操作系统的内存有关。

17、说一下 JVM 有哪些垃圾回收器?

Java的堆内存分为新生代和老年代:新生代主要存放短生命周期的对象,适合使用复制算法进行垃圾回收;老年代主要存放长生命周期的对象,适合标记整理算法进行垃圾回收。因此JVM针对新生代和老年代提供了多种不同的垃圾收集器:

针对新生代提供的垃圾回收器有:Serial、ParNew、Parallel Scavenge[ˈsɪəriəl]/[ˈpærəlel][ˈskævɪndʒ]


针对新生代提供的垃圾回收器有:CMS、Serial Old、Parallel Old、G1

**CMS 垃圾回收器(Concurrent Mark Sweep)**是为老年代设计的垃圾回收器,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。基于线程的标记-清除算法来实现。
CMS缺点:

CMS回收器采用的基础算法是Mark-Sweep。所有CMS不会整理、压缩堆空间。
这样就会有一个问题:经过CMS收集的堆会产生空间碎片  

CMS适用场景:

如果应用程序对停顿比较敏感,并且在应用程序运行的时候可以提供更大的内存和更多的CPU(也就是硬件牛逼),
那么建议使用CMS垃圾回收器,还有,如果在JVM中,有相对较多存活时间较长的对象会更适合使用CMS

**G1 垃圾回收器(Garbage First)**将堆内存划分为大小固定的几个独立区域,独立使用这些区域的内存资源并跟踪这些区域的垃圾收集进度,同时在后台维护了一个优先级列表,优先回收垃圾最多的区域,确保了G1垃圾收集器在有限的时间内获得最高的垃圾收集效率。 相比CMS,G1的2个优点:

基于标记-整理算法,不产生内存碎片
在不牺牲吞吐量的前提下实现短停顿垃圾回收。

18、说一下 JVM 调优的工具?

JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具 [kənˈsəʊl]

jconsole:用于对 JVM 中的内存、线程和类等进行监控;

jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、GC 变化等。

19、常用的 JVM 调优的常用配置参数都有哪些?

• -Xms

s为strating,表示堆内存起始大小。-Xms2g:初始化堆大小为 2g

• -Xmx

x为max,表示最大的堆内存。-Xmx2g:堆最大内存为 2g

• -Xmn

n为new,表示新生代大小

• -XX:NewRatio=4:

设置年轻的和老年代的内存比例为 1:4 

• -XX:+DisableExplicirGC

表示是否(+表示是,-表示否)打开GC日志详细信息