一、 JVM内存区域相关
1. JVM的内存模型以及分区情况和作用
jvm内存结构如图所示
方法区
它存储的是已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
堆
堆内存最大,堆是被线程共享,堆的目的就是存放对象。几乎所有的对象实例都在此分配。当然,随着优化技术的更新,某些数据也会被放在栈上等。
枪打出头鸟,树大招风。因为堆占用内存空间最大,堆也是Java垃圾回收的主要区域(重点对象),因此也称作“GC堆”(Garbage Collected Heap)。
程序计数器
占用内存较小,现成私有。
程序计数器的作用可以看做是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变计数器的值来选取下一条字节码指令。其中,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器来完成。
本地方法栈
与虚拟机栈作用相似
区别在于虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈是为虚拟机使用到的Native方法服务。
虚拟机栈
虚拟机栈线程私有,生命周期与线程相同。
2. Java内存分配
寄存器:在程序中无法控制;
栈:存放基本类型的数据和对象的引用,但是对象本身不存放在栈中,而是存放在堆中;速度快,可以共享
堆:存放用new产生的数据;存取速度慢
静态域:存放在对象中用static定义的静态成员;
常量池:存放常量。
3. JVM堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space) ?
JVM 的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM 启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。
堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。
永久代是用于存放静态文件,如Java 类、方法等。
4. Java中会存在内存泄漏?
内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。
理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。
但是,即使这样,Java也还是存在着内存泄漏的情况。
1、长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露。
尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是Java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
2、当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。
二、 垃圾回收相关
1. GC是什么?为什么要有GC?
GC是垃圾收集的意思,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,从而有效的防止内存泄露。要请求垃圾收集,可以调用下面的方法之一:System.gc()或Runtime.getRuntime().gc(),但JVM可以屏蔽掉显示的垃圾回收调用。
2. Java的垃圾回收机制?
在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
3. 如何判断一个对象是否存活?
1、引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。但是这种方法很难解决对象之间相互循环引用的问题。
2、可达性分析
通过GC Root对象为起点,从这些节点向下搜索,搜索所走过的路径叫引用链,当一个对象到GC Root没有任何的引用链相连时,说明这个对象是不可用的。
可作为GC Roots的对象有哪些?
1、虚拟机栈(栈帧中的本地变量表)中引用的对象
2、本地方法栈中JNI(Native方法)引用的对象
3、方法区中类静态属性引用的对象
4、方法区中常量引用的对象
5、所有被同步锁(synchronized关键字)持有的对象。
4. 垃圾回收的优点和原理。说说2种回收机制。
与C语言开发人员需要手动进行内存资源的释放不同,Java提供垃圾回收机制,自动进行GC,将开发人员从容易犯错的内存资源管理中解放出来。
原理:当某个一个对象,没有任何引用指向它的时候,那么它就满足垃圾回收的条件,在适当的时候,JVM虚拟机进行GC将其回收,释放空间,以供后续再利用。
两种常见的回收机制:
1.定时回收: 每隔30分钟进行一次回收,这种机制的弊端是如果垃圾产生的比较快,有可能30分钟之内垃圾已经把内存占用光了,导致性能变慢
2.当垃圾占到某个百分比的时候,进行回收:比如,当垃圾占到70%的时候,进行回收。 这种机制的弊端是,如果垃圾产生的频率很快,那么JVM就必须高频率的进行垃圾回收。 而在垃圾回收的过程中, JVM会停顿下来,只做垃圾回收,而影响业务功能的正常运行。
一般说来 JVM会采用两种机制结合的方式进行垃圾回收。
5. 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。 通过这种方式确定哪些对象是"可达的”,哪些对象是”不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。
可以马上收回内存
程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。强制执行垃圾回收:System.gc()。Runtime.getRuntime().gc()
6. 什么是分布式垃圾回收 (DGC) ? 它是如何工作的?
DGC 叫做分布式垃圾回收。 RMI 使用 DGC 来做自动垃圾回收。因为 RMI 包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。 DGC 使用引用计数算法来给远程对象提供自动内存管理。
7. 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?
吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。而串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够了。
8. 在Java中,对象什么时候可以被垃圾回收?
当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。
9. 简述Java内存分配与回收策率以及Minor GC和Major GC?
-
对象优先在堆区的Eden区分配。
-
大对象直接进入老年代。
-
长期存活的对象直接进入老年代。
回收:当Eden区没有足够的空间分配时,虚拟机会执行一次Minor GC .Minor GC通常发生在Eden新生代,因为这个区的对象生存期短,发生频率高,回收速度快。Major GC发生在老年代,一般触发老年代的GC不会触发Minor GC ,但是通过配置,可以在之前进行一次Minor GC,能加快老年代的回收速度。
10. JVM中垃圾收集器有哪些?他们特点分别是什么?
新生代垃圾收集器
1.Serial串行收集器-复制算法
Serial收集器是新生代单线程收集器,优点是简单高效,算是最基本、发展历史最悠久的收集器。它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集完成。
Serial收集器依然是虚拟机运行在Client模式下默认新生代收集器,对于运行在Client模式下的虚拟机来说是一个很好的选择。
2.ParNew收集器-复制算法
ParNew收集器是新生代并行收集器,其实就是Serial收集器的多线程版本。
除了使用多线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The Worl、对象分配规则、回收策略等都与Serial 收集器完全一样。
3.Parallel Scavenge(并行回收)收集器-复制算法
Parallel Scavenge收集器是新生代并行收集器,追求高吞吐量,高效利用 CPU。
该收集器的目标是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可用高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
老年代垃圾收集器
1.Serial Old 收集器-标记整理算法 Serial Old是Serial收集器的老年代版本,它同样是一个单线程(串行)收集器,使用标记整理算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用。
如果在Server模式下,主要两大用途:
(1)在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用
(2)作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
2.Parallel Old 收集器-标记整理算法
Parallel Old 是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器在1.6中才开始提供。
3.CMS收集器-标记整理算法
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对前面几种收集器来说更复杂一些,整个过程分为4个步骤:
(1)初始标记;
(2)并发标记;
(3)重新标记;
(4)并发清除。
其中,初始标记、重新标记这两个步骤仍然需要“Stop The World”
CMS收集器主要优点:
- 并发收集;
- 低停顿。
CMS三个明显的缺点:
(1)CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。在JDK1.5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活;
(2)CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。在JDK1.5的默认设置下,CMS收集器当老年代使用了68%的空间后就会被激活。
(3)CMS是基于“标记-清除”算法实现的收集器,手机结束时会有大量空间碎片产生。空间碎片过多,可能会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前出发FullGC。
新生代和老年代垃圾收集器
1.G1收集器-标记整理算法
JDK1.7后全新的回收器, 用于取代CMS收集器。
G1收集器的优势:
1、特的分代垃圾回收器,分代GC: 分代收集器, 同时兼顾年轻代和老年代;
2、区算法, 不要求eden, 年轻代或老年代的空间都连续;
3、回收期间, 可由多个线程同时工作, 有效利用多核cpu资源;
4、回收过程中, 会进行适当对象移动, 减少空间碎片;
5、见性: G1可选取部分区域进行回收, 可以缩小回收范围, 减少全局停顿。
G1收集器的运作大致可划分为一下步骤:
G1收集器的阶段分以下几个步骤:
1、初始标记(它标记了从GC Root开始直接可达的对象);
2、并发标记(从GC Roots开始对堆中对象进行可达性分析,找出存活对象);
3、最终标记(标记那些在并发标记阶段发生变化的对象,将被回收);
4、筛选回收(首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region)。
11. Java中垃圾收集的方法有哪些?
一、引用计数算法(Reference Counting)
介绍:给对象添加一个引用计数器,每当一个地方引用它时,数据器加1;当引用失效时,计数器减1;计数器为0的即可被回收。
优点:实现简单,判断效率高
缺点:很难解决对象之间的相互循环引用(objA.instance = objB; objB.instance = objA)的问题,所以java语言并没有选用引用计数法管理内存。
二、根搜索算法(GC Root Tracing)
Java和C#都是使用根搜索算法来判断对象是否存活。通过一系列的名为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时(用图论来说就是GC Root到这个对象不可达时),证明该对象是可以被回收的。
在Java中哪些对象可以成为GC Root?
虚拟机栈(栈帧中的本地变量表)中的引用对象
方法区中的类静态属性引用的对象
方法区中的常量引用对象
本地方法栈中JNI(即Native方法)的引用对象
三、标记-清除算法(Mark-Sweep)
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记那些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:
1.效率不高,标记和清除的效率都很低;
2.会产生大量不连续的内存碎片,导致以后程序在分配交大的对象时,由于没有充足的连续内存而提前触发一次GC动作。
四、复制算法(Copying)
为了解决效率问题,复制算法将可用内存按容量划分相等的两部分,然后每次只使用其中的一块,当第一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清除完第一块内存,在将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一块内存。
于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大的那份内存叫Eden区,其余两块较小的内存叫Survior区。每次都会先使用Eden区,若Eden区满,就将对象赋值到第二块内存上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制赋值到老年代中。(java堆又分为新生代和老年代)。
五、标记-整理算法(Mark-Compact)
该算法是为了解决标记-清楚,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收的对象移动一端,然后清除掉端边界以为的对象,这样就不会产生内存碎片。
六、分代收集算法(Generational Collection)
根据对象的存活周期的不同将内存划分为几块,一般就分为新生代和老年代,根据各个年代的特点采用不同的收集算法。新生代(少量存活)用复制算法,老年代(对象存活率高)“标记-清理”算法。
三、类加裁相关
1.什么是类加载器,类加载器有哪些?
类加载器就是把类文件加载到虚拟机中,也就是说通过一个类的全限定名来获取描述该类的二进制字节流。
JVM为我们默认提供了系统类加载器(JDK1.8),包括:
Bootstrap ClassLoader(系统类加载器)
Extension ClassLoader(扩展类加载器)
Application ClassLoader(应用程序类加载器)
Customer ClassLoader(自定义加载器)
2. 类加载器双亲委派模型机制?
基本定义:
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器没有找到所需的类时,子加载器才会尝试去加载该类。
双亲委派机制:
1、 当 AppClassLoader 加载一个 class 时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器 ExtClassLoader 去完成。
2、 当 ExtClassLoader 加载一个 class 时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给 BootStrapClassLoader 去完成。
3、 如果 BootStrapClassLoader 加载失败,会使用 ExtClassLoader 来尝试加载;
4、 若 ExtClassLoader 也加载失败,则会使用 AppClassLoader 来加载,如果 AppClassLoader 也加载失败,则会报出异常 ClassNotFoundException。
如下图所示:
双亲委派作用:
1、 通过带有优先级的层级关可以避免类的重复加载;
2、 保证 Java 程序安全稳定运行,Java 核心 API 定义类型不会被随意替换。
3.什么是Class文件? Class文件主要的信息结构有哪些?
class文件就是将java源代码编译后形成的,可被java虚拟机加载运行的文件。
对于Java虚拟机来说,只要是满足Java虚拟机规范的class文件都可以被加载。
Java 虚拟机规范规定 Class 文件格式采用一种类似与 C 语言结构体的微结构体来存储数据,这种伪结构体中只有两种数据类型:无符号数和表。
- 无符号数 属于基本的数据类型,以 u1、u2、u4、u8来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码结构构成的字符串值。
- 表 是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以「_info」结尾。表用于描述有层次关系的复合结构的数据,整个 Class 文件就是一张表,它由下表中所示的数据项构成。
四.JVM调优
1、线上常用的JVM参数有哪些?
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:新生代大小
-XX:NewRatio:设置新生代和老年代的比值。如:为3,表示年轻代与老年代比值为1:3
-XX:SurvivorRatio:新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:为3,表示Eden:Survivor=3:2,一个Survivor区占整个新生代的1/5
-XX:MaxTenuringThreshold:设置转入老年代的存活次数。如果是0,则直接跳过新生代进入老年代
-XX:PermSize、-XX:MaxPermSize:分别设置永久代最小大小与最大大小(Java8以前)
-XX:MetaspaceSize、-XX:MaxMetaspaceSize:分别设置元空间最小大小与最大大小(Java8以后)
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行老年代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器新生代收集方式为并行收集时,使用的CPU数。并行收集线程数。
2. JVM提供的常用工具
jps:查看所有的jvm进程,包括进程ID,进程启动的路径等等
jinfo: 查看虚拟机参数
jstat:查看虚拟机运行时信息
jstack: 查看线程堆栈
jmap: 导出堆到文件