JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

在金三银四的跳槽季中,很多小伙伴们都已经开始蠢蠢欲动了,所以特意为大家整理了一篇JVM的文章, 带大家深入了解JVM,希望在工作和面试中能帮助到你。

目录: 

一、JVM介绍

二、java类的加载机制

三、JVM内存结构

四、JVM垃圾收集器

一、Jvm介绍

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

二、java类的加载机制

  类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

  通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。

  从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。

  从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。

  通过网络加载class文件。把一个Java源文件动态编译,并执行加载。类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

三、JVM内存结构

JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;

方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。

  所有的数据和程序都是在运行数据区存放,它包括以下几部分:

  1、 Stack 栈

  栈也叫栈内存,是Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。问题出来了:栈中存的是那些数据呢?又什么是格式呢?

  栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循“先进后出”原则。

  那栈帧中到底存在着什么数据呢?栈帧中主要保存3类数据:本地变量(Local Variables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(Frame Data),包括类文件、方法等等。光说比较枯燥,我们画个图来理解一下Java栈,如下图所示:

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

图例在一个栈中有两个栈帧,栈帧2是最先被调用的方法,先入栈,然后方法2又调用了方法1,栈帧1处于栈顶的位置,栈帧2处于栈底,执行完毕后,依次弹出栈帧1和栈帧2,线程结束,栈释放。

  2、Heap 堆内存

  JVM中分为堆和方法区,堆又进一步分为新生代和老年代,方法区为永久代。

  堆中区分的新生代和老年代是为了垃圾回收,新生代中的对象存活期一般不长,而老年代中的对象存活期较长,所以当垃圾回收器回收内存时,新生代中垃圾回收效果较好,会回收大量的内存,而老年代中回收效果较差,内存回收不会太多。

  基于以上特性,新生代中一般采用复制算法,因为存活下来的对象是少数,所需要复制的对象少,而老年代对象存活多,不适合采用复制算法,一般是标记整理和标记清除算法。

  因为复制算法需要留出一块单独的内存空间来以备垃圾回收时复制对象使用,所以将新生代分为eden区和两个survivor区,每次使用eden和一个survivor区,另一个survivor作为备用的对象复制内存区。

  一个JVM实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:

  Permanent Space 永久存储区

  永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。

  Young Generation Space 新生区

  新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。

  Tenure generation space养老区

  养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。 三个区的示意图如下:

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

  3、 Method Area 方法区

  方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。

  4、PC Register 程序计数器

  每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。

5、 Native Method Stack 本地方法栈

四、JVM垃圾收集器

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

  如上图所示,垃圾回收算法一共有7个,3个属于年轻代、三个属于年老代,G1属于横跨年轻代和年老代的算法。

JVM会从年轻代和年老代各选出一个算法进行组合, 连线表示哪些算法可以组合使用。

  1、Serial(年轻代)

  年轻代收集器,可以和Serial Old、CMS组合使用

  采用复制算法

  使用单线程进行垃圾回收,回收时会导致Stop The World,用户进程停止

  client模式年轻代默认算法

  GC日志关键字:DefNew(Default New Generation)

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

  2、ParNew(年轻代)

  新生代收集器,可以和Serial Old、CMS组合使用

  采用复制算法

  使用多线程进行垃圾回收,回收时会导致Stop The World,其它策略和Serial一样

  server模式年轻代默认算法

  使用-XX:ParallelGCthreads参数来限制垃圾回收的线程数

  GC日志关键字:ParNew(Parallel New Generation)

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

  3、Paralle Scavenge(年轻代)

  新生代收集器,可以和Serial Old、Parallel组合使用,不能和CMS组合使用

  采用复制算法,使用多线程进行垃圾回收,回收时会导致Stop The World

  关注系统吞吐量

  -XX:MaxGCPauseMillis:设置大于0的毫秒数,收集器尽可能在该时间内完成垃圾回收

  -XX:GCTimeRatio:大于0小于100的整数,即垃圾回收时间占总时间的比率,设置越小则希望垃圾回收所占时间越小,CPU能花更多的时间进行系统操作,提高吞吐量

  -XX:UseAdaptiveSizePolicy:参数开关,启动后系统动态自适应调节各参数,如-Xmn、-XX:SurvivorRatio等参数,这是和ParNew收集器重要的区别

  GC日志关键字:PSYoungGen

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

  4、Serial Old(年老代)

  年老代收集器,可以和所有的年轻代收集器组合使用(Serial收集器的年老代版本)

  采用 ”标记-整理“算法,会对垃圾回收导致的内存碎片进行整理

  使用单线程进行垃圾回收,回收时会导致Stop The World,用户进程停止

  GC日志关键字:Tenured

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

  5、Parallel Old(年老代)

  年老代收集器,只能和Parallel Scavenge组合使用(Parallel Scavenge收集器的年老代版本)

  采用 ”标记-整理“算法,会对垃圾回收导致的内存碎片进行整理

  关注吞吐量的系统可以将Parallel Scavenge+Parallel Old组合使用

  GC日志关键字:ParOldGen

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

  6、CMS(Concurrent Mark Sweep年老代)

  年老代收集器,可以和Serial、ParNew组合使用

  采用 ”标记-清除“算法,可以通过设置参数在垃圾回收时进行内存碎片的整理

  (1)UserCMSCompactAtFullCollection:默认开启,FullGC时进行内存碎片整理,整理时用户进程需停止,即发生Stop The World

  (2)CMSFullGCsBeforeCompaction:设置执行多少次不压缩的Full GC后,执行一个带压缩的(默认为0,表示每次进入Full GC时都进行碎片整理)

  CMS是并发算法,表示垃圾回收和用户进行同时进行,但是不是所有阶段都同时进行,在初始标记、重新标记阶段还是需要Stop the World。CMS垃圾回收分这四个阶段

  (1)初始标记(CMS Initial mark) Stop the World 仅仅标记一下GC Roots能直接关联到的对象,速度快

  (2)并发标记(CMS concurrent mark) 进行GC Roots Tracing,时间长,不发生用户进程停顿

 (3)重新标记(CMS remark) Stop the World 修正并发标记期间因用户程序继续运行导致标记变动的那一部分对象的标记记录,停顿时间较长,但远比并发标记时间短

 (4)并发清除(CMS concurrent sweep) 清除的同时用户进程会导致新的垃圾,时间长,不发生用户进程停顿

  适合于对响应时间要求高的系统

  GC日志关键字:CMS-initial-mark、CMS-concurrent-mark-start、CMS-concurrent-mark、CMS-concurrent-preclean-start、CMS-concurrent-preclean、CMS-concurrent-sweep、CMS-concurrent-reset等等

  缺点:

  (1)对CPU资源非常敏感

  (2)CMS收集器无法处理浮动垃圾,即清除时用户进程同时产生的垃圾,只能等到下次GC时回收

  (3)因为是使用“标记-清除”算法,所以会产生大量碎片。

  7、G1

G1的设计原则就是简单可行的性能调优。

  开发人员仅仅需要声明以下参数即可:

  -XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200

  其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g 设计堆内存的最大内存为32G,-XX:MaxGCPauseMillis=200设置GC的最大暂停时间为200ms。如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。

  其次,G1将新生代,老年代的物理空间划分取消了。

  这样我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

  取而代之的是,G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

JVM详解篇:大厂面试都会问这个问题,你知道吗?

 

在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值