198. 什么是双亲委派模型?
双亲委派模型:类加载器试图加载某个类型的时候,除非父加载器找不到相应的类型,否则将这个任务代理给当前加载器的父加载器去做。
使用委派模型的目的是避免重复加载java类型。
打破双亲委派模型:重写ClassLoader的loadCLass方法
199. 说一下类装载的执行过程?
前言一个Java文件从编码完成到最终执行,一般主要包括两个过程
编译
运行
编译,即把我们写好的java文件,通过javac命令编译成字节码,也就是我们常说的.class文件。
运行,则是把编译声称的.class文件交给Java虚拟机(JVM)执行。
而我们所说的类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
举个通俗点的例子来说,JVM在执行某段代码时,遇到了class A, 然而此时内存中并没有class A的相关信息,
于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。
由此可见,JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。
类加载
类加载的过程主要分为三个部分:加载、链接、初始化
而链接又可以细分为三个小部分:验证、准备、解析
加载
简单来说,加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。
这里有两个重点:
字节码来源。一般的加载来源包括从本地路径下编译生成的.class文件,
从jar包中的.class文件,从远程网络,以及动态代理实时编译
类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
注:为什么会有自定义类加载器?
一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,
然后再通过实现自己的自定义类加载器进行解密,最后再加载。
另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。
验证
主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?
对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?
对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。
对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?
校验符号引用中的访问性(private,public等)是否可被当前类访问?
准备
主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。
特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。
比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,final static tmp = 456,
那么该阶段tmp的初值就是456
解析
将常量池内的符号引用替换为直接引用的过程。
两个重点:
符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,
实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量
举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程。
换句话说,只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
200. 怎么判断对象是否可以被回收?
下面我们正式介绍Java对象是否存活的判断算法——根搜索算法。这个算法的思路其实很简单,它把内存中的每一个对象都看作一个节点,
并且定义了一些对象作为根节点“GC Roots”。如果一个对象中有另一个对象的引用,那么就认为第一个对象有一条指向第二个对象的边,
如下图所示。JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,
那么就认为这些对象已经没有用了,需要被回收。
201. Java 中都有哪些引用类型?
强引用(strongreference)就是指在程序代码之中普遍存在的,类似“Object obj=new Object()” 这类的引用,只要强引用还存在,
垃圾收集器永远不会回收掉被引用的对象实例。
软引用(softreference)是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象, 在系统将要发生内存溢出异常之前,
将会把这些对象实例列进回收范围之中进行 第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。
弱引用(weakreference)也是用来描述非必需对象的,但是它的强度比软引用更弱一些,
被弱 引用关联的对象实例只能生存到下一次垃圾收集发生之前。
当垃圾收集器工作时, 无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。
在 JDK 1.2 之 后,提供了 WeakReference 类来实现弱引用。
虚引用(phantomreference)也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象 实例是否有虚引用的存在,
完全不会对其生存时间构成影响,也无法通过虚引用 来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象
实例被收集器回收时收到一个系统通知。在 JDK 1.2 之后,提供了 PhantomReference 类来实现虚引用。
202. 说一下 JVM 有哪些垃圾回收算法?
常用的垃圾回收算法有如下四种:标记-清除、复制、标记-整理和分代收集。
标记-清除算法
从算法的名称上可以看出,这个算法分为两部分,标记和清除。首先标记出所有需要被回收的对象,
然后在标记完成后统一回收掉所有被标记的对象。
这个算法简单,但是有两个缺点:
一是标记和清除的效率不是很高;二是标记和清除后会产生很多的内存碎片,导致可用的内存空间不连续,
当分配大对象的时候,没有足够的空间时不得不提前触发一次垃圾回收。
复制算法
这个算法将可用的内存空间分为大小相等的两块,每次只是用其中的一块,
当这一块被用完的时候,就将还存活的对象复制到另一块中,
然后把原已使用过的那一块内存空间一次回收掉。这个算法常用于新生代的垃圾回收。
复制算法解决了标记-清除算法的效率问题,以空间换时间,但是当存活对象非常多的时候,复制操作效率将会变低,
而且每次只能使用一半的内存空间,利用率不高。
标记-整理算法
这个算法分为三部分:一是标记出所有需要被回收的对象;二是把所有存活的对象都向一端移动;
三是把所有存活对象边界以外的内存空间都回收掉。
标记-整理算法解决了复制算法多复制效率低、空间利用率低的问题,同时也解决了内存碎片的问题。
分代收集算法
根据对象生存周期的不同将内存空间划分为不同的块,然后对不同的块使用不同的回收算法。一般把Java堆分为新生代和老年代,
新生代中对象的存活周期短,只有少量存活的对象,所以可以使用复制算法,而老年代中对象存活时间长,
而且对象比较多,所以可以采用标记-清除和标记-整理算法。
203. 说一下 JVM 有哪些垃圾回收器?
新生代收集器:
Serial
ParNew
Parallel Scavenge
老年代收集器:
Serial Old
CMS
Parallel Old
堆内存垃圾收集器:
G1
204. 详细介绍一下 CMS 垃圾回收器?
cms只会回收老年代和永久带(1.8开始为元数据区,需要设置CMSClassUnloadingEnabled),不会收集年轻带;
cms是一种预处理垃圾回收器,它不能等到old内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败;
所以cms垃圾回收器开始执行回收操作,有一个触发阈值,默认是老年代或永久带达到92%;
cms是最常用的垃圾垃圾回收器,下面分析下CMS垃圾回收器工作原理;
CMS 处理过程有七个步骤:
1. 初始标记(CMS-initial-mark) ,会导致swt;
2. 并发标记(CMS-concurrent-mark),与用户线程同时运行;
3. 预清理(CMS-concurrent-preclean),与用户线程同时运行;
4. 可被终止的预清理(CMS-concurrent-abortable-preclean) 与用户线程同时运行;
5. 重新标记(CMS-remark) ,会导致swt;
6. 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
7. 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;
205. 新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,
缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
206. 简述分代垃圾回收器是怎么工作的?
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,
它的执行流程如下:
把 Eden + From Survivor 存活的对象放入 To Survivor 区;
清空 Eden 和 From Survivor 分区; From Survivor 和 To Survivor 分区交换,
From Survivor 变 To Survivor,To Survivor 变 From Survivor。
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,
当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。
以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
207. 说一下 JVM 调优的工具?
jconsole
Jconsole(Java Monitoring and Management Console)是从java5开始,
在JDK中自带的java监控和管理控制台,用于对JVM中内存,
线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监测工具。
jconsole使用jvm的扩展机制获取并展示虚拟机中运行的应用程序的性能和资源消耗等信息。
VisualVM
VisualVM 是一个工具,它提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM)
上运行的基于 Java 技术的应用程序(Java 应用程序)的详细信息。VisualVM 对 Java Development Kit (JDK)
工具所检索的 JVM 软件相关数据进行组织,并通过一种使您可以快速查看有关多个 Java 应用程序的数据的方式提供该信息。
您可以查看本地应用程序以及远程主机上运行的应用程序的相关数据。此外,还可以捕获有关 JVM 软件实例的数据,
并将该数据保存到本地系统,以供后期查看或与其他用户共享。
MAT
MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,
它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,
快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,
并可以通过报表直观的查看到可能造成这种结果的对象。
通常内存泄露分析被认为是一件很有难度的工作,一般由团队中的资深人士进行。
不过要介绍的 MAT(Eclipse Memory Analyzer)被认为是一个“傻瓜式“的堆转储文件分析工具,
你只需要轻轻点击一下鼠标就可以生成一个专业的分析报告。和其他内存泄露分析工具相比,MAT 的使用非常容易,
基本可以实现一键到位,即使是新手也能够很快上手使用。
GChisto
GChisto是一款专业分析gc日志的工具,可以通过gc日志来分析:Minor GC、full gc的时间、频率等等,
通过列表、报表、图表等不同的形式来反应gc的情况。虽然界面略显粗糙,但是功能还是不错的。
配置好本地的jdk环境之后,双击GChisto.jar,在弹出的输入框中点击 add 选择gc.log日志
gcviewer
GCViewer也是一款分析小工具,用于可视化查看由Sun / Oracle, IBM, HP 和 BEA Java 虚拟机产生的垃圾收集器的日志,
gcviewer个人感觉显示 的界面比较乱没有GChisto更专业一些。
208. 常用的 JVM 调优的参数都有哪些?
例如:-Xms20m -Xmx20m -Xss256k
jvm配置
XX比X的稳定性更差,并且版本更新不会进行通知和说明。
1、-Xms
?? ?s为strating,表示堆内存起始大小
2、-Xmx
?? ?x为max,表示最大的堆内存
(一般来说-Xms和-Xmx的设置为相同大小,因为当heap自动扩容时,会发生内存抖动,影响程序的稳定性)
3、-Xmn
?? ?n为new,表示新生代大小
(-Xss:规定了每个线程虚拟机栈(堆栈)的大小)
4、-XX:SurvivorRator=8?
?? ?表示堆内存中新生代、老年代和永久代的比为8:1:1
5、-XX:PretenureSizeThreshold=3145728
?? ?表示当创建(new)的对象大于3M的时候直接进入老年代
6、-XX:MaxTenuringThreshold=15?? ?
?? ?表示当对象的存活的年龄(minor gc一次加1)大于多少时,进入老年代
7、-XX:-DisableExplicirGC
?? ?表示是否(+表示是,-表示否)打开GC日志