• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

JVM全方位解读(附面试题)

互联网 diligentman 5天前 5次浏览

java中就虚拟机是其他语言编写的(C语言+汇编语言,因此,JVM最常出现的攻击就是buffer overflow),如javac命令等,而java api是java写的,大多开源在openjdk,jdk中有一个src.jar,就是JDk的源码,本文是JVM基础知识的一个汇总,方便查阅,内容较多,以下是内容目录,可以直接跳到感兴趣的章节。

  1、JVM的内存模型

  2、堆栈的异常总结

  3、常用的JVM参数设置

  4、JVM的分代介绍

  5、GC的回收过程

  6、GC的回收算法

  7、GC的回收器

  8、JVM的优化

  9、JVM的内存分析工具

  10、对象的创建

  11、对象的内存布局

  12、对象的监视器

  13、类加载器、反射、双亲委派

1、JVM的内存模型

JDK7内存模型(图来自于网络):                       

JVM全方位解读(附面试题)

JDK8内存模型(图来自于网络):

JVM全方位解读(附面试题)

JVM内存模型说明:

 JDK7中内存模型包括:方法区,堆区,栈区,本地方法栈,计数器,分为两个类型的区域,一种是线程私有的,一种是线程共享的,其中,方法区和堆区是线程共享的,其他都是线程私有的,堆区是被GC的主要区域,方法区有部分废弃的常量可以被GC,下面详细的说明:

 (1)计数器,线程私有,当前线程所执行的字节码的行号指示器,标记当前执行到了哪一行指令

 (2)虚拟机栈,线程私有,生命周期和线程相同,存局部变量表、操作数栈、动态链接、方法出口(即方法返回地址)信息等

  其中,局部变量表存编译器可知的各种数据类型,包括boolean、char、byte、short、int、float、double,及对象的引用

 (3)本地方法栈,和虚拟机栈所发挥的作用非常相似,区别是,虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的 局部变量表、操作数栈、动态链接、出口信息

 (4)堆,java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对 象实例以及数组都在这里分配内存

 (5)方法区,用于存储已被虚拟机加载的类的信息(字段、方法)、常量、静态变量、即时编辑器编译后的代码等数据,运行时常量池:属于方法区,包含字面量(字符串、final常量)、符号引用

  其中,方法区的详细说明:

  方法区,Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫 做 Non-Heap(非堆),目的应该是与 Java 堆区分开来,方法区也被称为永久代,但方法区和永久代并不完全等同,只是为了便于管理,这样,HotSpot虚拟机的垃圾收集器就可以像管理java堆一样管理这部分内存,方法区可被GC回收的那部分内存是废弃的常量,但这样管理并不好,会容易产生内存溢出,所以在JDK8中,移除了方法区,将方法区中的常量池移至堆中(字符串池和类的静态变量放入java堆中),其他移到直接内存中,命名为“元空间”,解决了永久代会出现内存溢出的问题,但是要注意,需要设置元空间的最大大小(-XX:MaxMetaspaceSize设置),否则,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存

 (6)直接内存,不属于JVM运行时数据区,JVM的NIO方法可以分配堆外内存如使用 DirectByteBuffer

2、堆栈的异常总结

   (1)堆内存溢出OutOfMemoryError:java heap space

   产生原因:java堆用于存储对象实例,只要不断的创建对象,并保证GC roots到对象间有可达路径避免这些对象的GC,那么,当对象数量到达堆的最大容量限制后就会产生OOM

   解决办法:

  1. 通过参数 -XX:HeapDumpOnOutOfMemoryError 可以让虚拟机在内存溢出异常时Dump当前内存堆转储快照
  2. 通过内存映像分析工具(如:Eclipse Memory Analyzer)对Dump出的堆转储快照分析,判断是内存泄露还是内存溢出
  3. 如果是内存泄露:通过工具查看泄露对象的类型信息和它们到 GC Roots 的引用链信息,分析GC收集器无法自动回收它们的原因,定位内存泄露的代码位置
  4. 如果是内存溢出:检查堆参数 -Xms和-Xmx,看是否可调大;代码上检查某些对象生命周期过长,持有时间过长的情况,尝试减少程序运行期间内存消耗

  (2)除程序计数器外,JVM其他几个运行时区域都可能发生OutOfMemoryError异常

  (3)栈的两种异常

    1. StackOverFlowError异常:线程请求的栈深度大于虚拟机所允许的最大深度

    一个会发生stackOverFlow的场景:无限递归,没有出口

    2.OutOfMemoryError异常:虚拟机扩展栈时无法申请足够的内存空间

    一个会发生OutOfMemory的场景:list,无限添加元素

    解决虚拟机栈两种异常的办法:  

      1.检查代码中是否有死递归

      2.配置 -Xss 增大每个线程的栈内存容量,但会减少工作线程数,需要权衡

3、常用的JVM参数设置

  (1)-Xss,设置栈的大小,不熟悉最好使用默认值,当栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常

  (2) 堆内存设置:

    ① -Xms,初始堆大小,Server端JVM最好将-Xms和-Xmx设为相同值,开发测试机JVM可以保留默认值

    ② -Xmx,最大堆大小,默认为物理内存的1/4,最佳设值应该视物理内存大小及计算机内其他内存开销而定

     默认空余堆内存小于 40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、 -Xmx相等以避    免在每次GC 后调整堆的大小。

    ③ -Xmn:年轻代大小(young区大小),通常为 Xmx 的 1/3 或 1/4,不熟悉最好保留默认值

    ④ -XX:SurvivorRatio,设置年轻代Eden和单个S区的比例,默认为8,即Eden为8/10,两个survivor分别为1/10,其中一个survivor闲置,用于复制,所以年轻代实际可用的内存    大小为-Xmn设置值的9/10,Eden设置的太大的话,会导致GC变慢,并且没有足够的survivor幸存者空间,会导致GC直接到达老年代,老年代满的更快,会更早触发FullfGC,     Eden设置的过小,则MinorGC频繁,会影响线上程序运行,因为GC会导致应用程序暂停

    ⑤ -XX:MaxTenuringThreshold,设置年轻代中回收区对象的年龄,默认15,可通过命令指定,如果设置为0,表示Eden回收时,不经过Survivor,直接到达老年代

   (3) 持久代设置:

    ① -XX:PermSize,方法区(永久代,或称非堆区)初始分配的内存大小,其全称为permanent size(持久化内存)

    ② -XX:MaxPermSize=64m,永久代的最大大小,超过这个值,将会抛出OutOfMemoryError:PermGen

     在配置之前一定要慎重的考虑一下自身软件所需要的非堆区内存大小,因为持久代内存是不会被java垃圾回收机制进行处理的地方。

     最大堆内存与最大非堆内存的和绝对不能够超出操作系统的可用内存。

    (4) 元空间的设置:

    JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始 了),取而代之是元空间,元空间使用的是直接内存。配置如下:

    -XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)

    -XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小

    与永久代很大的不同就是,元空间解决了永久代易发生内存溢出的问题,但是要注意,如果不指定元空间的大小,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。

 4、JVM的分代介绍

  因为GC垃圾回收的主要区域是堆区,从GC的角度来说,java堆又细分为新生代和老年代,另外有一部分是持久代,来代表方法区,是为了方便管理,是方法区在堆区开辟出来的一块逻辑空间,下图为分代示意图(图片来自于网络)

  JVM全方位解读(附面试题)

  (1)新生代,新生代又分为三个区域,包括Eden区和两个Survivor幸存者区,Eden区主要存放new出来的对象,Survivor主要存放上一次GC之后的幸存者,作为这一次GC的被扫描者,分别对应图中的S0和S1,两个Survivor区等大,其中一个闲置,所以新生代实际可用的内存大小要减去其中一个幸存者区域的大小

  (2)老年代,老年代主要存放大对象,或从MinorGC过来的到达一定年龄仍然幸存的对象

  (3)持久代,即方法区,用于存放已被虚拟机加载的类的信息,静态变量,常量,和编译后的代码等信息,持久代能被GC的是废弃的常量,持久代对垃圾回收没有显著影响

 5、GC的回收过程

  (1) MinorGC的过程:MinorGC采用复制算法,首先,把Eden区域和使用的幸存者区域(SurvivorFrom)中存活的对象复制到另一块空闲的幸存者区(SurvivorTo)中,同时,把这些对象的年龄+1,如果有对象的年龄已经达到了老年代的标准,则赋值到老年代区,如果空闲的幸存者区(SurvivorTo)不够存放Eden和使用的幸存者区(SurvivorFrom)移动过来的数据,则直接放到老年代,然后,清空Eden和使用的幸存者区中(SurvivorFrom)的对象,然后,幸存者区互换,SurvivorTo中的数据换到SurvivorFrom中,SurvivorFrom继续等待下一次GC,Survivor区每熬过一次MinorGC,就将对象的年龄+1,当对象的年龄到达某个值时(默认时15,可用通过参数-XX:MaxTenuringThreshold 来设定),这些对象就会成为老年代

   (2) MajorGC的过程:老年代的回收,不会那么频繁,老年代使用的回收算法是标记-清除或标记-整理算法,标记-清除算法会产生内存碎片,即不连续的空间,如果此时,有大的对象进来,内存中没有足够的连续空间时,会提前触发FullGC(这是一个优化点),一次FullGC的时间要比一次MinorGC的时间长,当年老代也装不下,就会抛出OOM(Out Of Memory)异常  (3) Full GC的触发:老年代满了而无法容纳更多的对象,会触发Full GC,Full GC 清理整个内存堆,包括年轻代和老年代。

 6、GC的回收算法

  (1) 标记-清除算法,缺点是容易产生碎片,且效率不高,标记过程和清除过程效率都不高

    标记-清除算法的过程,分为两个过程,标记过程和清除过程

    ① 标记过程,遍历所有的GC-roots,然后将所有GC-roots可达的对象标记为存活的对象(记为1)

    ② 清除过程,遍历堆中的所有对象,将没有标记的对象全部清除掉(没有标记过的,记为0)

    ③ 清除过后,被标记过的对象留下,标志位重新归0

  以下是标记-清除算法的图示(图片来自于网络)

   JVM全方位解读(附面试题)

  (2) 复制算法,用于年轻代,需要额外的空间来进行复制操作

    复制算法的过程,就是把内存分为2块等同大小的内存空间(A和B),使用A进行内存的使用,当A内存不足以分配对象而引起内存回收时,就会把存活的对象从A内存块放到B内    存块,然后把A内存块中的对象全部清除掉,然后在B内存块中使用,当B内存不足以分配对象而引起内存回收时,就会把存活的对象从B内存块放到A内存块中,然后把B内存    块中的对象全部清除掉,如此循环

    复制算法的好处是,避免的空间碎片(内存中不连续的空间),缺点是浪费了一半的空间,降低空间使用率

   以下是复制算法的图示(图片来自于网络)

   JVM全方位解读(附面试题)  

  (3) 标记-整理算法(Mark-Compact),用于老年代

    标记-整理算法的过程,标记过程仍然与标记-清除算法一样,但后续步骤不是直接 对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以     外的内存

  以下是标记-整理算法的图示(图片来自于网络)

   JVM全方位解读(附面试题)

   从图中可以看出,标记-整理算法,避免了标记-清除算法产生内存碎片的问题, 也避免了复制算法中内存浪费的问题,存在的问题就是效率问题,比前两者效率低 

  (4) 分代收集,JVM实际GC中使用的,根据对象存活周期的不同,分新生代和老年代,新生代使用复制算法,因为每次GC只有少量的对象存活,用复制算法只需要付出少量存活对    象的复制成本就可以完成收集,老年代使用标记-整理算法或标记-清除算法,老年代中,对象存活率高,没有额外的担保空间,就必须使用标记-清除或标记-整理算法

   以下是分代收集算法的图示(图片来自于网络)

  JVM全方位解读(附面试题)

7、GC的回收器

1、收集器

(1) serial收集器,是单线程收集器,用于新生代,使用复制算法,在GC时,会暂停其他所有工作的线程,直到GC结束,常用于client模式下的虚拟机

(2) parNew收集器,是serial的多线程版本,也用于新生代,使用复制算法

parNew和serial都可以且只能和老年代的CMS和serial old组合使用

(3) Parallel Scarenge收集器,用于新生代,使用复制算法,主要关注吞吐量,适合在后台运算且不需要太多交互的任务

(4) Serial old收集器,是serial收集器的老年代版本,是单线程收集器,使用标记-整理算法,也是给client下的虚拟机用

(5) Parallel old,用于老年代,使用标记-整理算法,注重吞吐量,及CPU敏感的场合优先考虑使用parallel + parallel old组合

(6) CMS(concurrent Mark ………


喜欢 (0)