Java虚拟机

1. 简述JVM内存模型

  线程私有的运行时数据区:程序计数器、Java虚拟机栈、本地方法栈。

  线程共享的运行时数据区:Java堆、方法区。

2. 简述程序计数器

  程序计数器表示当前线程所执行的字节码的行号指示器。

  程序计数器不会产生StackOverflowError和OutOfMemoryError异常。

3. 简述虚拟机栈

  Java虚拟机栈用来描述Java方法执行的内存模型。线程创建时就会分配一个栈空间,线程结束后占空间被回收。

  栈中元素用于支持虚拟机进行方法调用,每个方法在执行时都会创建一个栈帧存储方法的局部变量表、操作栈、动态链接和返回地址等信息。

  虚拟机栈会产生StackOverflowError和OutOfMemoryError异常。

  StackOverflowError:线程请求的栈深度大于虚拟机允许的深度抛出。

  OutOfMemoryError:如果JVM栈容量可以动态扩展,虚拟机栈占用内存超出抛出。

4. 战术本地方法栈

  本地方法栈与虚拟机栈作用相似,不同的是虚拟机栈为虚拟机执行Java方法服务,本地方法栈为本地方法服务。可以将虚拟机栈看作普通的Java函数对应的内存模型,本地方法栈看作由native关键词修饰的函数对应的内存模型。

  本地方法栈会产生StackOverflowError和OutOfMemoryError。

5. 简述JVM中的堆

  堆主要作用是存放对象实例,Java里几乎所有的对象实例都在堆分配内存,堆也是内存管理中最大的一块。Java的垃圾回收主要就是针对这一区域进行。可通过-Xms和-Xmx设置堆得最小和最大容量。

  堆会抛出OutOfMemoryError异常。

6. 简述方法区

  方法区用于存储被虚拟机加载的类信息、常量、静态变量等数据。

  JDK6之前用永久代实现方法区,容易内存溢出。JDK7把放在永久代的字符串常量池、静态变量等移出,JDK8中抛弃永久代,改用在本地内存中实现的元空间来实现方法区,把JDK7中永久代内容移到元空间。

  方法区会抛出OutOfMemoryError异常。

7. 简述运行时常量池

  运行时常量池存放常量池表,用于存放编译器生成的各种字面量与符号引用。一般除了保存Class文件中描述的符号引用外,还会把符号引用翻译的直接引用也存储在运行时常量池。除此之外,也会存放字符串基本类型。

  JDK8之前,放在方法区,大小受限于方法区。JDK8将运行时常量池存放在堆中。

8. 简述直接内存

  直接内存也称为堆外内存,就是把内存对象分配在JVM堆外的内存区域。这部分内存不是虚拟机管理,而是由操作系统来管理。Java通过DriectByteBuffer对其进行操作,避免了在Java堆和Native堆来回复制数据。

9. 简述Java创建对象的过程

  ① 检查该指令的参数能否在常量池中定位到一个类的符号引用,并检查引用代表的类是否已被加载、解析和初始化。如果没有就先执行类加载;

  ② 通过检查后,虚拟机将为新生对象分配内存;

  ③ 完成内存分配后,虚拟机将为成员变量设为零值;

  ④ 设置对象头,包括哈希码、GC信息、锁信息、对象所属类的类元信息等;

  ⑤ 执行init方法,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

10. 简述JVM给对象分配内存的策略

  ① 指针碰撞:这种方式在内存中放一个指针作为分界指示器,将使用过的内存放在一边,空闲的放在另一边,通过指针挪动完成分配。

  ② 空闲列表:对于Java堆内存不规整的情况,虚拟机必须维护一个列表记录那些内存可用,在分配时从列表中找到一块足够大的空间分给对象并更新列表记录。

11. Java对象内存分配是如何保证线程安全的

  ① 对分配内存空间采用CAS机制,配合失败重试的方式保证更新操作的原子性。该方式效率低。

  ② 每个线程在Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块“私有”内存中分配。一般采用这种策略。

12. 简述对象的内存布局

  对象在堆内存的存储布局可分为对象头、实例数据和对齐填充。

  对象头主要包含两部分数据:MarkWord和类型指针。MarkWord用于存储哈希码(HashCode)、GC分代年龄、锁状态标志位、线程持有的锁、偏向线程ID等信息。类型指针即对象指向他的类元数据指针,如果对象是一个Java数据,会有一块用于记录数组长度的数据。

  实例数据存储代码中所定义的各种类型的字段信息。

  对齐填充起占位作用。HotStop虚拟机要求对象的起始地址必须是8的整数倍,因此需要对齐填充。

13. 如何判断对象是否是垃圾

  引用计数法:设置引用计数器,对象被引用计数器加一,引用失效计时器减一,如果计数器为零则被标记为垃圾。会存在对象间循环引用的问题,一般不适用这种方法。

  可达性分析:通过GC Roots的根对象作为起始节点,从这些节点开始,根据引用关系向下搜索,如果某个对象没有被搜到,则会标记为垃圾。可作为GC Roots的对象包括虚拟机栈和本地方法栈中引用的对象、类静态属性引用的对象、常量引用的对象。

14. 简述Java的引用类型

  强引用:被强引用关联的对象不会被回收。一般采用new方法创建强引用。

  软引用:被软引用关联的对象只有在内存不够的情况下才会被回收。一般采用SoftReference类来创建软引用。

  弱引用:垃圾收集器碰到即回收,也就是说它只能存活到下一次垃圾回收发生之前。一般采用WeakReference类来创建弱引用。

  虚引用:无法通过该引用获取对象、唯一目的就是为了能在对象被回收时收到一个系统通知。虚引用必须与引用队列联合使用。

15. 简述标记清除算法、标记整理算法和标记复制算法

  标记清除算法:先标记需清除的对象,之后统一回收。这种方法效率不高,会产生大量不连续的碎片。

  标记整理算法:先标记存活对象,然后让所有存活对象向后一端移动,之后清理端边界以外的内存。

  标记复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当使用的这块空间用完了,就将存活对象复制到另一块,再把已使用过的内存空间一次清理掉。

16. 简述分代收集算法

  根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。一般将堆分成新生代和老年代,对这两块采用不同的算法。

  新生代使用标记复制算法;老年代使用标记清除或者标记整理算法。

17. 简述Serial垃圾收集器

  单线程串行收集器。垃圾回收的时候,必须暂停其他所有线程。新生代使用标记复制算法,老年代使用标记整理算法。简单高效。

18. 简述ParNew垃圾收集器

  可以看做Serial垃圾收集器的多线程版本,新生代使用标记复制算法,老年代使用标记整理算法。

19. 简述Parallel Scavenge垃圾收集器

  注重吞吐量,即CPU运行代码时间/CPU耗时总时间(CPU运行代码时间+垃圾回收时间)。

  新生代使用标记复制算法,老年代使用标记整理算法。

20. 简述GMS垃圾收集器

  注重最短时间停顿。GMS垃圾收集器为最早提出的并发收集器,垃圾收集线程与用户线程同时工作。采用标记清除算法。该收集器分为初始标记、并发标记、并发预清理、并发重置这么几个步骤。

  初始标记:暂停其他线程(stop the world),标记与GC Roots直接关联的对象。

  并发标记:可达性分析过程(程序不会停顿)。

  并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象,重新标记,暂停虚拟机(stop the world)扫描GMS堆中剩余对象。

  并发清除:清除垃圾对象(程序不会停顿)。

  并发重置:重置GMS筹集器的数据结构。

21. 简述G1垃圾收集器

  和之前收集器不同,该垃圾收集器把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。通过引入Region的概念,从而将原来的一整块内存空间划分成多个小空间,使得每个小空间可以单独进行垃圾回收。

  初始标记:标记与GC Roots直接关联的对象。

  并发标记:可达性分析。

  最终标记:对并发标记过程中,用户线程修改的对象再标记一下。

  筛选回收:对各个Region的回收价值和成本进行排序,然后根据用户所期望的GC停顿时间制定回收计划并回收。

22. 简述Minor GC

  Minor GC指发生在新生代的垃圾收集,因为Java对象大多存活时间短,所以Minor GC非常频繁,一般回收速度也比较快。

23. 简述Full GC

  Full GC是清理整个堆空间,包括年轻代和永久代。调用System.gc(),老年代空间不足,空间分配担保失败,永久代空间不足会产生Full GC。

24. 常见内存分配策略

   大多数情况下对象在新生代Eden区分配,当Eden没有足够空间时将发起一次Minor GC。

  大对象需要大量连续内存空间,直接进入老年代区分配。

  如果经历过第一次Minor GC仍然存活且能被Survivor容纳,该对象就会被移动到Survivor中并将年龄设置为1,并且每熬过一次Minor GC年龄就加一,当增加到一定程度(默认15)就会被晋升到老年代。

  如果在Survivor中相同年龄所有对象大小的总和大于Survivor的一半,年龄不小于该年龄的对象就可以直接进入老年代。

  空间分配担保。Minor GC前虚拟机必须检查老年代最大可用连续空间是否大于新生代对象总空间,如果满足则说明这次Minor GC确定安全。否则JVM会查看HandlePromotionFailure参数是否允许担保失败,如果允许会继续检查老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小,如果满足将Minor GC,否则改成一次Full GC。

 

posted @   三五-七言  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示