Java-JVM

JVM与调优21题

Java 类加载过程?

加载:查找和导入Class文件。
验证:检查载入Class文件数据的正确性。
准备:给类的静态变量分配存储空间。
解析:将符号引用转成直接引用。
初始化:对类的静态变量,静态代码块执行初始化操作。
使用
卸载

描述一下 JVM 加载 Class 文件的原理机制?

Java中的所有类都是由类加载器加载到JVM中才能运行,其中类加载器也是一个class类,它主要负责将class文件由硬盘加载到内存文件中。
类装载方式:
显式装载。通过class.forname()来动态加载class类。
隐式装载。在程序运行中遇到了new Clazz()新建对象,就需要隐式的调用对应的类到JVM中。
Java类加载是动态的,并不是全部加载到内存中,而是保证程序运行的基础上根据程序运行的需要再加载。

Java的类加载器一共有三种:
BootstrapLoader:负责加载系统类
ExtClassLoader:负责加载扩展类
AppClassLoader:负责加载应用类
那加载器之间如何协同工作呢?答案就是委托机制,工作原理就是但类加载器需要加载类时,先让父级加载器去加载,如果父类还有父类,则继续向上传递,如果父类加载器加载失败就自己加载。好处在于避免重复加载和核心类的修改风险。

Java 内存分配。

堆:(线程共享)
堆:对象实例
方法区:类信息、常量、静态变量、
栈:
程序计数器:程序运行行号指示器
虚拟机栈:Java方法执行内存模型
本地方法栈:本地方法执行

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

GC 即垃圾收集,是指 JVM 用于释放那些不再使用对象所占用的内存。
在编程中,内存管理是开发人员最容易出现问题的地方,忘记或者错误的内存回收都会导致程序的崩溃。

简述 Java 垃圾回收机制

在Java 中不需要开发人员手动的回收不需要的内存空间,因此关于如何确定该块内存不再使用、如何回收内存、什么时候回收内存就成了问题。
如何确定该块内存不再需要:
引用计数法: 在这种方法中,堆中每个对象实例都有一个引用计数器。当一个对象被创建,且该对象实例分配给一个变量,则计数器加1。当一个对象实例内存被回收后,该实例引用对象的计数器就是减1。最后计数器变为0,就会回收该块内存。在出现相互依赖时,该对象实例内存就很难被回收。
可达性分析算法: 通过一些称为引用链(GC Roots)的对象作为起点,从这些节点向下搜索,搜索过的路径被称为调用链,当一个对象到 GC Roots 没有调用链时,则表明该对象不可用。
强引用: new 新建对象实例。
软引用: SoftReference,内存空间足够,不会回收它,内存不足就会回收。
弱引用: WeakReference,GC 回收都会回收。
虚引用: PhantomReference,没有任何意义,只要GC 回收,都会被回收。
如何回收内存:
标记清除算法: 首先将内存中需要清除的区域进行标记,随后将标记的区域进行清除。但是这回导致需要小碎片似的内存空间而无法使用。
复制算法: 在标记清除算法的基础上,将内存分为两块,一块内存使用完,然后将存活的对象复制到另一块内存上,以此往复。
标记整理算法: 首先将内存中的清除的区域进行标记,然后将存活的对象进行向一端移动,再清除掉边界以外的区域。
内存模型和回收策略:
Java Heap(堆)是JVM 管理最大的一块,堆是垃圾收集器的主要收集区域。
Java 堆中主要分为两个区域-年轻代和老年代。其中年轻代又分为 Eden 区域和 Survivor 区域,其中 Survivor 区域又分为 From 和 To 两个分区。

如何判断一个对象是否存活?(或者 GC 对象的判定方法)

引用计数法: 在这种方法中,堆中每个对象实例都有一个引用计数器。当一个对象被创建,且该对象实例分配给一个变量,则计数器加1。当一个对象实例内存被回收后,该实例引用对象的计数器就是减1。最后计数器变为0,就会回收该块内存。在出现相互依赖时,该对象实例内存就很难被回收。
可达性分析算法: 通过一些称为引用链(GC Roots)的对象作为起点,从这些节点向下搜索,搜索过的路径被称为调用链,当一个对象到 GC Roots 没有调用链时,则表明该对象不可用。

垃圾回收的优点。并考虑 2 种回收机制。

开发过程中不需要考虑内存管理问题。可以防止内存泄漏。
如何回收内存:
标记清除算法: 首先将内存中需要清除的区域进行标记,随后将标记的区域进行清除。但是这回导致需要小碎片似的内存空间而无法使用。
复制算法: 在标记清除算法的基础上,将内存分为两块,一块内存使用完,然后将存活的对象复制到另一块内存上,以此往复。
标记整理算法: 首先将内存中的清除的区域进行标记,然后将存活的对象进行向一端移动,再清除掉边界以外的区域。

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

垃圾回收器通常是作为一个低级别的线程运行,在不可预知的情况下对堆中已经死亡的对象或者长时间未使用的对象进行清除和回收。
开发人员不能实时的调用垃圾回收器对某个对象进行内存回收。可以通过 System.gc() 或者 Runtime.getRunTime().gc() 进行GC 处理。

Java 中会存在内存泄漏吗,请简单描述

内存泄露: 对象已经没有被使用,但是垃圾回收器却无法清除它们,因为还在被引用着。
内存泄露类型
堆内存溢出:OutOfMemoryError:Java heap space,设置的JVM堆内存太小,而新建对象太大导致堆内存溢出,修改JVM设置参数即可。
堆内存泄漏:OutOfMemoryError,堆中的一些对象没有被使用,但是却不能被垃圾清理器清除的情况。根据实际代码情况进行修改。
垃圾回收超时内存溢出:OutOfMemoryError,应用程序耗尽了所有可用的内存时,GC开销的时间超过了错误,而GC多次也未能清除。减少对象生命周期,尽量快速的进行垃圾回收。
栈内存溢出:OutOfMemoryError,检查代码并修改。
本地线程内存溢出:线程池使用不当,导致堆以外内存区域无法分配线程,检查配置堆内存是否足够,然后检查代码。
JVM设置参数描述:
-Xmx:Java Heap 最大值,默认值为物理内存的1/4,最佳设值应该视物理内存大小及计算机内其他内存开销而定。
-Xms:Java Heap 初始值,Server 端JVM最好将 -Xms 和 -Xmx 设为相同值,开发测试机JVM可以保留默认值。
-Xmn:Java Heap Young 区大小,不熟悉最好保留默认值。
-Xss:每个线程的 Stack 大小,不熟悉最好保留默认值。

深拷贝和浅拷贝。

深拷贝和浅拷贝针对的是包含引用数据类型这样的。
深拷贝:会创建一个与原始对象一样的对象,不共享内存,修改新对象不会影响到旧对象。
浅拷贝:只复制原始对象的指针,而不是对象本身,新旧对象共享一块内存,无论新旧对象修改都会影响到另一个对象。

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

java.lang.System.gc() 只是 java.lang.Runtime.getRuntime().gc() 的简写,两者的行为没有任何不同。
唯一要能说有什么不同那就是在字节码层面上调用前者比调用后者短一点点,前者是1条字节码而后者是2条。

finalize() 方法什么时候被调用?析构函数 (finalization) 的目的是什么?

finalize() 方法调用时机:在对象被第一次被标记过程中,会判断是否执行 finalize() 方法,如果对象实现了finalize()方法并且没有被虚拟机调用过,稍后就会有一个低优先级的线程触发 finalize() 方法。
析构函数的是使对象逃脱内存回收的最后一次机会,同时当初也是为了帮助C++ 的开发人员更快的上手Java,目前由于运行代价等原因不推荐使用。

final、finally、finalize 区别

final Java关键字,修饰常量、方法、类,分别表示不可变常量、不可覆盖方法、不可继承类。
finally 异常处理语句的一部分,表示总是执行。
finalize 在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收或者避免此对象被回收。

如果对象的引用被置为 null,垃圾收集器是否会立即释放对象占用的内存?

不会立即释放对象的内存,只是断开了当前线程栈中对该对象的引用关系,而垃圾收集器在后台运行时,用户线程到达线程安全点会扫描对象引用关系,扫描到对象没有引用关系就会被标记,只有真正确认对应无法恢复引用的时候才会被清除对象内存。

什么是分布式垃圾回收(DGC)?它是如何工作的?

串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?

吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。
串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够。

在 Java 中,对象什么时候可以被垃圾回收?

对象在没有引用关系,同时不会被所使用的应用程序再次使用时就会被垃圾回收。

简述 Java 内存分配 Minor GC 和 Major GC。

内存分配:
栈:
程序计数器:是当前线程所执行的行号指示器,跳转指令等都依赖这个完成,线程私有。
虚拟机栈:Java方法执行的线程内存模型。
本地方法栈:
堆:
方法区:被所有线程共享,用于存放已被虚拟机加载的信息,常量,静态变量等,是Java虚拟机描述为堆的一个逻辑部分。习惯被称为永久代。
堆:堆被所有线程共享,在虚拟机启动时创建,是唯一的目的是存放对象实例,是gc的主要区域。通常可分为两个区块年轻代和年老代。更新一点年轻代可分为Eden区,主要放新创建对象,From survivor 和 To survivor 保存 gc 后幸存下的对象,默认情况下各自占比 8:1:1。
当 Eden 区没有足够的空间分配时,虚拟机会执行一次Minor GC。Minor GC 通常发生在Eden新生代,因为这个区的对象生存期短,发生频率高,回收速度快。
Major GC 发生在老年代,一般触发老年代的 GC 不会触发 Minor GC,但是通过配置,可以在之前进行一次Minor GC,能加快老年代的回收速度。

CMS与G1

CMS与G1:

开始几点要清晰:
1、不同的分代,会采用的收集器是不同的。一般来说新生代收集频率高需要采用性能较好且高效的收集器。而老年代收集频率低,对空间比较敏感。
2、在垃圾收集执行的时候,应用程序应当暂停运行。
3、可以做串行化收集,也可以做并行化收集。
4、如果能够做到并行收集和收集行为可控那就是很棒的事情。

CMS收集器:
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。因为CMS 收集器工作时GC线程可以与工作线程并发执行,以此达到降低停止停顿时间的目的。
CMS 收集器主要用于老年代的收集,基于标记-清除算法实现,运行过程主要分为4个步骤:初始标记。并发标记。重新标记。并发清除。
其中初始标记和重新标记这两个步骤仍然需要 stop-the-world 。初始标记仅仅只是标记了以下GC Roots能够到达的对象,速度很快。并发标记就是GC Roots Tracing 的过程。而重新标记阶段则是为了修正并发标记期间因用户程序继续运行而导致的标记变动的那一部分对象而已,这个阶段的停顿时间一般比初始阶段稍微长一点,但是比并发标记短的多。
CMS收集器优点:并发收集、停顿时间短。缺点:对CPU资源很敏感、无法处理浮动垃圾、基于标记-清除算法,该算法的缺点都有(内存碎片化等)。
CMS收集器之所以能够做到并发,根本原因在于采用基于“标记-清除”的算法并对算法过程进行了细粒度的分解。

安全点:即程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。安全点的初始目标并不是使其他线程停下来,而是找到一个稳定的执行状态,在这个状态下JAVA 的堆栈不再发生变化,这样垃圾回收器就可以稳定的执行可达性分析,并且程序也可以继续运行代码。
“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint。
安全区域:指在一段代码片段中,引用关系不会发生变化。在这个区域中任意地方开始GC都是安全的。也可以把Safe Region看作是被扩展了的Safepoint。

G1收集器:
G1收集器重新分配了堆空间,打破了原有的分代模型,将堆空间划分为一个个的区域。这样做的目的在于在进行垃圾回收时,没有必要对全部堆空间进行回收。区域划分的好处就是停顿时间的可预测收集模型:用户可以执行多长时间内完成收集。
G1运行过程:
1、初始标记。仅仅只是标记一下GC Roots 可以到达的对象,并且修改TAMS 的值,让下一个阶段的标记过程中能够在正确的Regin 中创建新对象,这个阶段需要停顿线程,但是耗时很短。
2、并发标记。从GC Roots开始堆中的可达性分析,找出存活的对象,这个阶段耗时较长,但是可以与程序并发执行。
3、最终标记。为了修正在并发标记阶段因为程序运行而导致的标记变化的那部分,这部分需要停顿线程,但是可以并发执行。
4、筛选回收。首先对各个Regin回收价值和成本进行排序,然后根据用户的设置值来制定回收计划。因为只回收一部分的Regin,时间是可控的,会大幅提高收集效率。
G1具备以下特点:
1、并行与并发。G1充分利用多CPU和多核环境,使用多个CPU来缩短 stop-the-world 停顿的时间,原先其他收集器需要停顿程序的执行的GC操作,G1收集器仍然可以通过并发的方式让程序继续运行。
2、分代收集。
3、空间整合。与CMS不同,G1 是基于标记-整理算法实现的收集器,局部来看(两个Regin之间)通过复制算法实现。
4、可预测的停顿。原因在于它可以有计划的避免整个堆空间的进行全区域的垃圾收集。G1通过一个算法计算出每个Regin收集所需的成本并量化,这样一来再给定的收集时间要求下总可以找到一组恰当的Ragin作为收集目标。
5、G1将堆空间分成许多个相同大小的区域单元(Regin),Regin是一块连续的内存空间。虽然还保留着新生代和老年代的概念,但是两者之间已经不再是物理隔离的状态。Region的大小是一致的,数值是在1M到32M字节之间的一个2的幂值数,这个可以手动调整也可以根据堆大小自动调整。

JVM 的永久代中会发生垃圾回收么?

永久代不会进行垃圾回收,永久代满了,会触发 full gc,由于比较消耗资源,需要通过设置合适永久代大小避免 full gc。(Java8中已经移除了永久代,新加了一个叫做元空间的区域在计算机内存中)

什么是类加载器,类加载器有哪些?

类加载器:在java程序中 .class 文件保存着编译后的虚拟机指令,当需要时就将class 文件加载到虚拟机的内存中,这个过程就成为类加载。
类加载器类别:
启动(Bootstrap)类加载器:主要加载JVM自身需要的类。
扩展(Extension)类加载器:
系统(System)类加载器:
自定义类加载器:

类加载器双亲委派模型机制?

双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下:自定义类加载器->系统类加载器->扩展类加载器->引导类加载器。