Java 常见的面试题(JVM)

一、说一下 jvm 的主要组成部分?及其作用?

JVM包括 类加载子系统、堆、方法区、栈、本地方法栈、程序计数器、直接内存、垃圾回收器、执行引擎。

  1. 类加载子系统:类加载子系统负责加载class信息,加载的类信息存放于方法区中。
  2. 直接内存:直接内存是在Java堆外的、直接向系统申请的内存空间。访问直接内存的速度会由于Java堆。出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。
  3. 垃圾回收器:垃圾回收器可以对堆、方法区、直接内存进行回收。
  4. 执行引擎:执行引擎负责执行虚拟机的字节码,虚拟机会使用即时编译技术将方法编译成机器码后再执行。

二、说一下 jvm 运行时数据区?

运行时数据区包括:堆、方法区、栈、本地方法栈、程序计数器。

  1. 堆:堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。
  2. 方法区:方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。
  3. 栈:栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动态链接、方法出口等信息。
    1. 栈帧:每个方法从调用到执行的过程就是一个栈帧在虚拟机栈中入栈到出栈的过程。
    2. 局部变量表:用于保存函数的参数和局部变量。
    3. 操作数栈:操作数栈又称操作栈,大多数指令都是从这里弹出数据,执行运算,然后把结果压回操作数栈。
  4. 本地方法栈:与栈功能相同,本地方法栈执行的是本地方法,一个Java调用非Java代码的接口。
  5. 程序计数器(PC寄存器):程序计数器中存放的是当前线程所执行的字节码的行数。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。

三、说一下堆栈的区别?

  • 栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
  • 堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。

堆与栈的区别:

  1. 栈内存存储的是局部变量而堆内存存储的是实体;
  2. 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
  3. 栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。

四、队列和栈是什么?有什么区别?

队列(Queue):是限定只能在表的一端进行插入和在另一端进行删除操作的线性表;

栈(Stack):是限定只能在表的一端进行插入和删除操作的线性表。

区别如下:

  1. 规则不同
    1. 队列:先进先出(First In First Out)FIFO
    2. 栈:先进后出(First In Last Out )FILO
  2. 对插入和删除操作的限定不同
    1. 队列:只能在表的一端进行插入,并在表的另一端进行删除;
    2. 栈:只能在表的一端插入和删除。
  3. 遍历数据速度不同
    1. 队列:基于地址指针进行遍历,而且可以从头部或者尾部进行遍历,但不能同时遍历,无需开辟空间,因为在遍历的过程中不影响数据结构,所以遍历速度要快;
    2. 栈:只能从顶部取数据,也就是说最先进入栈底的,需要遍历整个栈才能取出来,而且在遍历数据的同时需要为数据开辟临时空间,保持数据在遍历前的一致性。
  4. 接口实现的异同:队列和栈由Collcetion接口实现,队列由Queue接口实现,栈由List接口实现。
  5. 遍历数据速度的异同:
    1. 栈只能从头部取数据,也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前后的一致性。
    2. 队列基于地址指针进行遍历,而且可以从头或尾部开始遍历,无需开辟临时空间,速度要快的多。

五、什么是双亲委派模型?

当需要加载一个类的时候,子类加载器并不会马上去加载,而是依次去请求父类加载器加载,一直往上请求到最高类加载器:启动类加载器。当启动类加载器加载不了的时候,依次往下让子类加载器进行加载。当达到最底下的时候,如果还是加载不到该类,就会出现ClassNotFound的情况。

好处:保证了程序的安全性。例子:比如我们重新写了一个String类,加载的时候并不会去加载到我们自己写的String类,因为当请求上到最高层的时候,启动类加载器发现自己能够加载String类,因此就不会加载到我们自己写的String类了。

六、说一下类加载的执行过程?

一个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修饰的变量或语句进行初始化。
  • 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
  • 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

七、怎么判断对象是否可以被回收?

  1. 引用计数算法(已被淘汰的算法):给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
    1. 目前主流的java虚拟机都摒弃掉了这种算法,最主要的原因是它很难解决对象 之间相互循环引用的问题。尽管该算法执行效率很高。
  2. 可达性分析算法:目前主流的编程语言(java,C#等)的主流实现中,都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如下图所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。

八、java 中都有哪些引用类型?

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判定对象是否存活都和“引用”离不开关系。如今,将引用分为强引用、软引用、弱引用和虚引用4种,强度依次减弱。

  1. 强引用:发生GC的时候不会被回收。
  2. 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
  3. 弱引用:有用但不是必须的对象,在下一次GC的时候会被回收。
  4. 虚引用:无法通过虚引用获得对象,用PhantomReference实现虚引用,虚引用的用途是在GC时返回一个通知

九、说一下 jvm 有哪些垃圾回收算法?

  1. 标记-清除算法:标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。
    1. Java 常见的面试题(JVM)
  2. 复制算法:复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集合(GC Roots)中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。
    1. Java 常见的面试题(JVM)
  3. 标记-整理算法:标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。具体流程见下图:
    1. Java 常见的面试题(JVM)
  4. 分代收集算法:分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法
    1. Java 常见的面试题(JVM)
    2. 年轻代(Young Generation)的回收算法
      1. 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
      2. 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
      3. 当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
      4. 新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。
    3.  年老代(Old Generation)的回收算法
      1. 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
      2. 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
    4. 持久代(Permanent Generation)的回收算法:用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代也称方法区,具体的回收可参见上文2.5节。

十、说一下 jvm 有哪些垃圾回收器?

Java 常见的面试题(JVM)

  • Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。
  • Serial Old收集器(标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本。
  • ParNew收集器(停止-复制算法):新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
  • Parallel Scavenge收集器(停止-复制算法):并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。
  • Parallel Old收集器(停止-复制算法):Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。
  • CMS(Concurrent Mark Sweep)收集器(标记-清理算法):高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。

十一、详细介绍一下 CMS 垃圾回收器?

  1. 总体介绍:CMS是一款优秀的垃圾收集器。众所周知,在oracle公司的Hotspot的架构中,大体上采用分代回收的机制。其中出生代又采用了拷贝复制的方法。如果对象在初生代内存活超过一定次数之后,就可以晋升到老生代中,而CMS垃圾收集器就是专门用来对老生代做收集。随着现代硬件的发展,更多的企业及服务最关注的点在GC的停顿时间,而CMS恰好是一种以获取最短回收停顿时间为目标的收集器,可以给这类服务带来最好的服务效果。
  2. 算法剖析:Mostly-Concurrent collection 这个算法是由Boehm等人提出的,并设计了一种基于三色(tricolor)的收集器。它对整个老年代做一个写屏障,所以如果要对老年代做写操作的时候会讲对应的对象置为灰色。它主要分为以下四个阶段:
    1. 初始标记阶段:暂停所有的其他线程,并记录下直接与root相连的对象。
    2. 并发标记阶段:同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
    3. 最终确认标记阶段:将上一阶段做了指针更新的区域和root合并为一个伪root集合,并对其做tracing。从而可以保证真正可达的对象一定被标记了。但同时也会产生一部分被标记为可达,但其实已经是不可达的区域,由于已经没有了到达这个区域的路径,所以并没有办法将它的标志位置为0,则造成了一个暂时的内存泄漏,但这部分空间会在下一次收集阶段被清扫掉。
    4. 并发清扫阶段:开启用户线程,同时GC线程开始对为标记的区域做清扫。这个过程要注意不要清扫了刚被用户线程分配的对象。一个小trick就是在这个阶段,将所有新分配的对象置为可达的。
    5. 下面我们来看一个运用了Mostly-Concurrent collection算法的例子:
    6. 这里写图片描述
      1. 处于并发标记阶段,经历了初始阶段标记的a。进入并发标记,记录了b, c, d。
      2. 依旧处于并发标记阶段,b和e发生了指针更新,并且被collector将其对应域置为dirty。
      3. 处于最终确认标记阶段,对dirty区别做一个重新扫描,并且标记上d。注意此时c已经是垃圾了,但我们无法对它的标志位做更新。
      4. 处于并发清扫阶段,会将f清扫掉。而c只能等到下一轮的收集,再去回收它咯。
    7. Mostly Concurrent Collection in a Generational System
      1. 这里写图片描述
      2. 现代的JVM就是利用CMS收集器对它的老生代做收集。这中间涉及到具体的并发标记的时候,用到Card Table这个概念。因为在Hotspot JVM的世代回收过程中,新生代的空间会比较小,而老生代的空间会比较大。基于老生代空间大,变更小的特点,为了尽量减少GC引起的停顿时间,采用了停顿时间最短的CMS收集器。在CMS的并发标记的过程中,它会将整个老生代的空间切割为一个个block,每个block对应一个card。并且对整个老生代加上write barrier。从而在并发的标记过程中,用card来记录堆内发生写操作的区域。
      3. 在具体的实现write barrier时,Hosking和Moss发现操作系统提供的内存保护屏障开销太大了,所以他们重新设计了一个只要2到3条指令就可以实现同样功能的barrier,这样意味着更小的开销,从论文里看这应该是一个轻量级的软件层面的barrier。但这种基于虚拟存储的系统并不能区分对每个block中的基本类型变量做修改还是对其中的引用类型变量做修改,所以会出现很多dirty但并没有做指针更新的block。
      4. 由于card还是包含了很多其他信息。所以为了节省更多的空间提高局部性,又提出了Mod Union Table。将每个dirty位单独拉出来做成一个bitmap或者bytemap。从而减少空间开销,提高定位堆中dirty块的速度。
        在标记过程中,为了减少collector和mutator之间在读取Object头部而产生的相互干扰,我们并没有将mark bit放到Object的头部。而是将他们抽出来放到外部作为一个单独的数据结构。在具体的实现过程中,对heap内的每4Byte大小做一个对应的bit标记位。通常为了减少GC的停顿时间,我们会将所以root可达的对象放入到这个集合内,称为remember set。但考虑到GC的时候本身就是缺乏资源的,所以这些数据结构不应该占据大量的空间。所以具体的实现过程中只存取了与root直接相邻的对象。
  3. CMS的具体过程
    1. 初始标记(STW initial mark):在具体的实现的时候,考虑到GC停顿时间和空间利用开销的因素,我们选择了一个折中的办法。即用了一个外部的bitmap增强局部性尽量减小GC的停顿时间,同时在to-scanned-set内放了直接与root相连的object,并将其标志位置为1,这样可以减小空间的开销。注意这个阶段需要暂停所有的非GC线程。
    2. 并发标记(Concurrent marking):在第二阶段的并发标记的阶段,GC线程会线性的扫描整个堆的bitmap。在线性的遍历bitmap的过程中,如果发现一个标志位为1的object则将它压入to-scanned-set内(具体实现用一个stack来维护)。当stack满的时候,进入循环,将栈内的对象一个个弹出来并扫描其中的引用域。同时遵循如下的规则: (1)引用的对象地址比自己低,注意此时按线性去遍历bitmap已经走过了这个位,则我们要将当前的引用对象压入栈中,并对其标志做一次更新操作置为1。(2)引用对象地址比自己高,则只用将对应的bit标记一下即可,因为根据线性的扫描,之后会扫到他,从而做有关的压栈扫描处理。从而不断的做循环,直到这个栈为空并且扫描结束,注意这个过程发生在并发的阶段,这意味着除了collector在做标记还有mutator在制造垃圾。这种策略的缺陷在于它需要对整个堆的做线性遍历,而不是简单的tracing( tracing是一个图遍历的过程,其算法复杂度为O(N+E) )。
    3. 并发预清理(Concurrent Precleaning):为了尽量减少下一阶段STW的时间。增加了预清理的阶段,主要是对并发标记过程中留下的dirty位做一个tracing。注意这里是并发的执行,所以同时也会对card table和标记表做更新。一般实现采用不多循环遍历dirty表做tracing并对它做clean操作,直到预清理清楚了三分之一以上的dirty表则退出循环(也有的采用的是dirty数小于1000则退出循环)。
    4. 重新标记(STW Remark):暂停所有用户线程,从dirty和root入口做tracing,确保所有可达对象做了标记。
    5. 并发清理(Concurrent sweeping):并发的开启GC和用户线程,其中涉及到对相邻free block的整合问题。因为它可能会由mutator和collector对freeList的操作产生数据的不一致性。所以为了保证一致性,需要对空闲块的操作加上外部锁(exclusion lock)。
    6. 并发重置(Concurrent reset):对标记的表和记录dirty的表做清零。并且重置CMS收集器的其他数据结构,等待下一次垃圾回收。
  4. .总结
    1. 优点:
      1. 将stop-the-world的时间降到最低,能给电商网站用户带来最好的体验。
      2. 尽管CMS的GC线程对CPU的占用率会比较高,但在多核的服务器上还是展现了优越的特性,目前也被部署在国内的各大电商网站上。
    2. 缺点:
      1. 对CMS在单核和多核机器上做测试。发现CMS在收集过程中会大量占用CPU的时间。所以在第二个阶段会比较漫长,所以一般将其设置在多核机器上。并且对于CMS在单核机器上的表现设计了一套启发式控制。这种控制将收集器看作一个掠夺者,而收集器会尽量赶在用户线程分配新的对象之前完成收集的工作。同样也有可能会出现用户线程希望分配对象,但目前空间不够,则需要停下收集器,这样会让整个收集时间大大加长。所以这时候一搬会选择扩张堆的大小。
      2. Mark Sweep算法一直令人诟病的碎片问题,造成了堆空间的浪费以及利用率的下降。
      3. 需要较大的内存空间去运行,因为在很多并行的阶段,要考虑到用户程序运行时也要分配空间。所以一般选择在堆利用率达到一个常数的时候就开启CMS的收集。可以在VM argument里来设置这个阀值。(–XX:CMSInitiatingOccupancyFraction =n,n=0~100)
      4. 会产生浮动垃圾,由于CMS并发清理阶段用户线程还在运行着,伴随程序自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好等到下一次GC去处理。

十二、新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 区别:整堆回收器:G1 新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

十三、简述分代垃圾回收器是怎么工作的?

  • 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 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)时,升级为老生代。大对象也会直接进入老生代。
  • 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程

十四、说一下 jvm 调优的工具?

  • Jconsole : jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很详细的跟踪。
  • JProfiler:商业软件,需要付费。功能强大。
  • VisualVM:JDK自带,功能强大,与JProfiler类似。推荐。

十五、常用的 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日志

注:以上内容仅提供参考和交流,请勿用于商业用途,如有侵权联系本人删除!

注:此博客只是为了记忆相关知识点,大部分为网络上的文章,在此向各个文章的作者表示感谢!

版权声明:程序员胖胖胖虎阿 发表于 2022年10月21日 上午3:00。
转载请注明:Java 常见的面试题(JVM) | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...