JVM相关知识点

1.Java堆

Java堆是jvm内存中被所有线程共享的一块区域,该区域物理上可以不连续但是逻辑上连续即可。Java堆存放着程序运行中生成的对象实例数据。在jdk1.7及以上版本,Hotspot虚拟机将运行时常量池和静态变量从方法区中移到了Java堆中。每个对象在Java堆上分配内存时,并发场景下,虚拟机采用CAS自旋锁和TLAB的方式保证内存的安全分配。具体分配方式根据不同垃圾收集器大体上分为指针碰撞和空闲列表两种。基于对象垃圾回收的强弱分代假说,即大多数对象是朝生夕死的,熬过越多次垃圾回收的对象越不容易消亡;主流虚拟机将Java堆划分为新生代和老年代,默认比例1:2,可以通过jvm参数修改。其中新生代中又具体划分为一个Eden区和两个Survivor区,默认比例8:1:1,可以通过jvm参数修改。堆内存的大小也可以通过jvm参数指定。垃圾收集器主要就是收集堆中的数据,经过垃圾回收后仍然无法给新对象分配内存时,此区域会触发OOM错误。

2.虚拟机栈

虚拟机栈是jvm内存中线程私有的一块区域,它与线程的生命周期相同。虚拟机栈的存储单位是栈帧,每个方法的执行和结束代表着一个栈帧的入栈和出栈。每个栈帧中存储着局部变量表、操作数栈、动态链接、方法返回地址和一些附加信息。栈内存大小决定了方法调用的深度,方法调用深度过大,如递归调用时,容易引发StackOverflow错误,一个线程的创建就会伴随着创建一个虚拟机栈,而创建线程也会占用jvm内存,当jvm无法为新线程分配内存时,此区域也会引发OOM错误。虚拟机栈的大小可以通过jvm参数指定。

3.本地方法栈

本地方法栈与虚拟机栈类似,也是属于线程私有的一块区域。只不过本地方法栈是jvm执行的native方法,native方法是jdk环境提供的本地方法,由c或c++语言实现。此区域同样会有StackOverflow和OOM的风险。

4.方法区

方法区是jvm虚拟机的设计规范,其具体实现为永久代和元空间,此区域是所有线程共享的一块区域。在jdk1.6及以前,Hotspot虚拟机采用永久代的方式实现方法区。可以通过jvm参数设置永久代的大小。永久代中主要存放着类的Class字节码信息、静态变量、运行时常量池和JIT即时编译器编译后的代码,由于这些数据在程序运行中被回收的概率很小,但是却占据着宝贵的jvm内存,在一些热部署的场景下,加载大量类信息,很容易引发此区域OOM,然而这些风险在系统的正常运行中不易发现。所以从jdk1.7开始,考虑将永久代从jvm内存中移除,直接使用操作系统内存存储这些信息。jdk1.7时,已经将运行时常量池、静态变量从永久代中转移到堆中存储。到jdk1.8时,Hotspot虚拟机彻底将永久代从jvm内存中移除,转而使用MetaSpace代替永久代,metaSpace使用的是操作系统和的内存。因此不会再出现OOM的错误。(除非超过了操作系统的内存)。

5.运行时常量池

运行时常量池是程序运行中储存Class对象的常量信息,包括字面量和符号引用。一个Class文件对应一个Class常量池,在程序运行时,Class文件被加载到jvm内存中时,就会产生一个Class运行时常量池。在jdk1.6及以前,每个Class的运行时常量池中还存在一份字符串常量池,多个Class运行时常量池中就会存在多份字符串常量池,而这些字符串常量池中的常量有很多是重复的,所以浪费了很大的内存空间,于是jdk从1.7开始,将字符串常量池从每个Class运行时常量池中移出来,统一成一份存储在堆空间中。避免了内存空间的浪费。

6.直接内存

直接内存并不是Java虚拟机规范中定义的内存区域。jdk1.4时引入了NIO网络传输模型,它可以通过native函数库直接分配堆外内存,因为避免了Java堆和native堆来回复制数据的操作,在频繁进行IO操作的场景下性能显著提高,直接内存与Java堆内存相比,因为无法利用Java堆分配内存的优良特性,并发场景下申请直接内存更耗费性能,但是直接内存在频繁IO操作的场景下性能要由于Java堆内存。

7.为什么堆内存要分年轻代和老年代?

堆内存好比是一块收纳空间,如果没有合理的规划空间的划分,很容易造成频繁使用的东西难以找到,经常不用的东西随处可见,而且东西乱丢乱放也很容易造成空间资源的浪费。基于垃圾回收的强弱分代假说,大多数对象都是朝生夕死的,坚持越久不被回收的对象越不容易消亡,所以将堆内存划分为年轻代和老年代,目的就是将朝生夕死的对象和持续不死的对象划分开,从而提升垃圾回收的效率。

8.一个对象的创建过程

jvm识别到new指令时,会先检查指令的参数是否在运行时常量池中能定位到这个类的符号引用,并检查符号引用代表的类是否已经被加载过,解析和初始化过,如果没有,则会先执行相应的类加载过程,如果有,则会开始为该对象在堆空间分配内存。jvm将分配到的内存都初始化为零值,紧接着为对象设置对象头信息,包括类元数据信息、哈希值、GC分代年龄等。接下来会执行init方法,按照开发者意愿给对象中的变量赋值。至此,对象创建完毕。

9.一个对象的内存分配

对象在堆空间上分配内存,不同的垃圾收集器采用的方式有所不同,主要分为指针碰撞和空闲列表两种分配方式。如果堆内存是规整的,所有已使用的内存放到一边,空闲的放到另一边,中间维护一个指针作为分界点,当为新对象分配内存时,只需要把指针向空闲方向移动一段与新对象大小相等的实例,这种分配方式称为指针碰撞。如果堆内存是不规整的,jvm必须维护一个列表,记录哪些内存可以使用,在分配内存时,需要在列表中找到一块空间分配给对象,然后更新列表上的记录,这种分配方式称为空闲列表。对象在堆中分配内存时及其频繁的操作,为了解决线程安全的问题,jvm采取TLAB的方式,每个线程在堆中预先分配一小块内存,若TLAB分配内存失败,jvm会采取CAS自旋锁的方式直接在堆中分配内存,保障内存分配时的线程安全。

新对象大多数会在新生代的Eden区分配到内存,当新对象很大,Eden分配失败时,YoungGC触发前,首先会进行老年代空间担保判断,若老年代的可用内存小于新生代所有存活对象占用的内存大小,会再次判断老年代的可用内存是否小于之前YoungGC后新生代对象进入老年代的平均大小,如果小于,直接触发FullGC,FullGC后若新对象能够成功分配内存,则直接在老年代中分配内存,若分配失败,报OOM错误;如果大于,首先会触发一次YoungGC,YoungGC后,新生代中存活的对象小于老年代可用内存的前提下,会将新生代中的存活对象年龄默认超过15直接进入到老年代;Survivor区中的存活对象大小超过了该区域的50%时,会将大于等于这批存活对象年龄最大值的所有对象直接进入到老年代;若新生代中存活对象大小超过Survivor区,也会直接进入到老年代。如果YoungGC后,新生代中存活的对象大小超过老年代可用内存,会再次触发FullGC,FullGC后继续将新生代存活对象进入到老年代,失败报OOM,新生代对象进入老年代成功后,在老年代为新对象直接分配内存,失败报OOM。

10.一个对象的销毁过程

当为新对象分配内存失败时,会触发垃圾回收,回收没有被引用的垃圾对象。不同的垃圾收集器回收垃圾的方式各不相同,但总体流程一样,通过标记-复制、标记-整理、标记清除算法找到没有被引用的对象,从而进行回收,即对象被销毁。

11.对象的两种访问方式

对象访问方式分为两种:句柄访问换个直接指针,两种方式各有利弊。通过句柄访问的方式jvm会在堆内存中维护一个句柄池,虚拟机栈中的本地变量表存储的引用地址是句柄池中的指针地址,句柄池中再维护数据指针到对象类型数据的映射,这种方式优势是在对象被移动时只会改变句柄中的实例数据指针,而本地变量表中的reference本身不需要进行修改。劣势是会有两次指针定位的时间开销;直接指针的方式是本地变量中的reference直接存储对象的内存地址,这种方式的优势是通过地址可以直接访问到对象,减少了一次指针定位的时间开销,劣势是当对象地址发生变化时,本地变量表中的reference也要跟着变化。

12.为什么需要内存担保?

一是为了让大对象尽快进入老年代,而是FullGC比较消耗性能,通过内存担保机制避免FullGC发生的过于频繁。

13.垃圾收集算法有哪些?垃圾收集器有哪些?他们的特点是什么?

垃圾收集算法主要有标记-复制、标记-整理和标记-清除算法,三种算法各有利弊。标记-复制算法优点是内存空间不会出现碎片化,缺点是存在空间的浪费,一般收集新生代垃圾使用该算法;标记-整理算法优点是内存空间不会出现碎片化,缺点是整理内存空间消耗时间,性能较低;标记-清除算法优点是性能良好,缺点是容易造成空间碎片,且执行效率不稳定。标记-整理和标记清除算法常用在老年代的垃圾回收。垃圾收集器有Serial、Serial Old、Parallel Scavenge、Parallel Old、ParNew、CMS、G1、ZGC。从作用范围上说:Serial、Parallel Scavenge和ParNew是新生代垃圾收集器,均采用了标记-复制算法;Serial Old、Parallel Old和CMS是老年代垃圾收集器,其中Serial Old、Parallel Old采用了标记-整理算法,CMS采用了标记-清除算法;G1、ZGC是整堆的垃圾收集器,全局采用了标记-整理,局部采用了标记-复制算法。从使用上说:Serial可以搭配Serial Old和CMS;ParNew可以搭配CMS和Serial Old;Parallel Scavenge可以搭配Parallel Old和Serial Old。从收集效率上说:Serial、Serial Old是串行垃圾收集器,主要用于单核CPU的服务器;Parallel Scavenge、Parallel Old、ParNew是并行垃圾收集器,适用于多核CPU的服务器,Parallel Scavenge、Parallel Old以吞吐量优先进行垃圾回收。ParNew是Serial的多线程版,单核CPU不建议使用;CMS是并发垃圾收集器,注重低延时,减少对用户的影响;G1、ZGC都是新一代的整堆垃圾收集器,吞吐量和低延时都表现良好,G1适合在大内存的服务器上使用,支持最小堆内存是2G,最大堆内存是64G,会根据回收价值和成本对垃圾排序,根据用户设定的耗时,回收尽可能多的垃圾;ZGC在jdk11中才引入,jdk15中发布稳定版,是一种可扩展低延时的垃圾收集器,适合内存8MB-16TB,目前暂未广泛使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值