Java面试题 - JVM相关

01 JVM结构

在这里插入图片描述
PC寄存器:

  1. 每个线程拥有⼀个pc寄存器;
  2. 指向下⼀条指令的地址。

⽅法区:

  1. 保存装载的类的元信息:类型的常量池,字段、⽅法信息,⽅法字节码;jdk6时,String等常量信息置于⽅法区,jdk7移到了堆中;
  2. 通常和永久区(Perm)关联在⼀起;

堆:

  1. 应⽤系统对象都保存在java堆中;
  2. 所有线程共享java堆;
  3. 对分代GC来说,堆也是分代的;

栈:

  1. 线程私有;
  2. 栈由⼀系列帧组成(因此java栈也叫做帧栈);
  3. 帧保存⼀个⽅法的局部变量(局部变量表)、操作数栈、常量池指针;

在这里插入图片描述

在这里插入图片描述
每⼀次⽅法调⽤创建⼀个帧,并压栈。

02 JVM内存模型

  1. 每⼀个线程有⼀个⼯作内存,和主存独⽴;
  2. ⼯作内存存放主存中变量的值的拷⻉;
  3. 对于普通变量,⼀个线程中更新的值,不能⻢上反应在其他变量中;如果需要在其他线程中⽴即可⻅,需要使⽤volatile关键字;
  4. volatile不能代替锁,⼀般认为volatile⽐锁性能好(不绝对),使⽤volatile的条件是语义是否满⾜应⽤;
  5. 可⻅性:⼀个线程修改了变量,其他线程可以⽴即知道。
  • ------------ volatile;
  • ------------ synchronized(unlock之前,写变量值回主存);
  • ------------ final(⼀旦初始化完成,其他线程可⻅)。

03 java四引⽤

  • 强引⽤:强引⽤是使⽤最普遍的引⽤。如果⼀个对象具有强引⽤,那垃圾回收器绝不会回收它。当内存空间不⾜,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终⽌,也不会靠随意回收具有强引⽤的对象来解决内存不⾜的问题。

  • 软引⽤:如果内存空间不⾜了,就会回收这些对象的内存。只要垃圾回收器没有回收它,软引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果软引⽤所引⽤的对象被垃圾回收器回收,Java虚拟机就会把这个软引⽤加⼊到与之关联的引⽤队列中。

  • 弱引⽤:弱引⽤与软引⽤的区别在于:只具有弱引⽤的对象拥有更短暂的⽣命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,⼀旦发现了只具有弱引⽤的对象,不管当前内存空间⾜够与否,都会回收它的内存。弱引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果弱引⽤所引⽤的对象被垃圾回收,Java虚拟机就会把这个弱引⽤加⼊到与之关联的引⽤队列中。

  • 虚引⽤:虚引⽤在任何时候都可能被垃圾回收器回收,主要⽤来跟踪对象被垃圾回收器回收的活动,被回收时会收到⼀个系统通知。虚引⽤与软引⽤和弱引⽤的⼀个区别在于:虚引⽤必须和引⽤队列 (ReferenceQueue)联合使⽤。当垃圾回收器准备回收⼀个对象时,如果发现它还有虚引⽤,就会在回收对象的内存之前,把这个虚引⽤加⼊到与之关联的引⽤队列中。

04 GC算法分类

引⽤计数法(没有被java采⽤):

  • 原理:对于⼀个对象A,只要有任何⼀个对象引⽤了A,则A的引⽤计数器就加1,当引⽤失效时,引⽤计数器就减1,只要对象A的引⽤计数器的值为0,则对象A就会被回收。
  • 问题:引⽤和去引⽤伴随加法和减法,影响性能;很难处理循环引⽤。

标记清除法:

  • 原理:现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。⼀种可⾏的实现是,在标记节点,⾸先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引⽤的垃圾对象。然后在清除阶段,清除所有未被标记的对象。
  • 问题:标记和清除两个过程效率不⾼,产⽣内存碎⽚导致需要分配较⼤对象时⽆法找到⾜够的连续内存⽽需要触发⼀次GC操作。

标记压缩法:

  • 原理:适合⽤于存活对象较多的场合,如⽼年代。它在标记-清除算法的基础上做了⼀些优化。标记阶段⼀样,但之后,将所有存活对象压缩到内存的⼀端。之后,清除边界外所有的空间。
  • 优点: 解决了标记- 清除算法导致的内存碎⽚问题和在存活率较⾼时复制算法效率低的问题。

复制算法:

  • 原理:将原有的内存空间分为两块,每次只使⽤其中⼀块,在垃圾回收时,将正在使⽤的内存中的存活对象复制到未使⽤的内存块中,之后清除正在使⽤的内存块中的所有对象,交换两个内存的⻆⾊,完成垃圾回收。
  • 问题: 不适⽤于存活对象⽐较多的场合,如⽼年代。

分代回收法:

  • 原理:根据对象存活周期的不同将内存划分为⼏块,⼀般是新⽣代和⽼年代,新⽣代基本采⽤复制算法,⽼年代采⽤标记整理算法。

05 MinorGC & FullGC

  1. Minor GC通常发⽣在新⽣代的Eden区,在这个区的对象⽣存期短,往往发⽣GC的频率较⾼,回收速度⽐较快,⼀般采⽤复制-回收算法。
  2. Full GC/Major GC 发⽣在⽼年代,⼀般情况下,触发⽼年代GC的时候不会触发Minor GC,所采⽤的是标记-清除算法。

06 垃圾收集器

  1. Serial New收集器是针对新⽣代的收集器,采⽤的是复制算法;
  2. Parallel New(并⾏)收集器,新⽣代采⽤复制算法,⽼年代采⽤标记整理;
  3. Parallel Scavenge(并⾏)收集器,针对新⽣代,采⽤复制收集算法;
  4. Serial Old(串⾏)收集器,新⽣代采⽤复制,⽼年代采⽤标记清理;
  5. Parallel Old(并⾏)收集器,针对⽼年代,标记整理;
  6. CMS收集器,基于标记清理;
  7. G1收集器(JDK):整体上是基于标记清理,局部采⽤复制;

综上:新⽣代基本采⽤复制算法,⽼年代采⽤标记整理算法,cms采⽤标记清理;

07 Java类加载机制

概念:

  • 虚拟机把描述类的数据⽂件(字节码)加载到内存,并对数据进⾏验证、准备、解析以及类初始化,最终形成可以被虚拟机直接使⽤的java类型(java.lang.Class对象)。

类⽣命周期:

  • 类加载过程:读取⼆进制字节流到jvm—>验证格式语义等—>为静态变量分配内存空间—>常量池引⽤解析—>执⾏static标识的代码。
  1. 加载过程:通过⼀个类的全限定名来获取定义此类的⼆进制字节流,将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构。在内存中(⽅法区)⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类的各种数据的访问⼊⼝;
  2. 验证过程:为了确保Class⽂件的字节流中包含的信息符合当前虚拟机的要求,⽂件格式验证、元数据验证、字节码验证、符号引⽤验证;
  3. 准备过程:正式为类属性分配内存并设置类属性初始值的阶段,这些内存都将在⽅法区中进⾏分配;

准备阶段,static对象会被设置默认值,static final对象会被赋上给予的值。

  1. 解析阶段:虚拟机将常量池内的符号引⽤替换为直接引⽤的过程。

i. 符号引⽤:字符串,引⽤对象不⼀定被加载;
ii. 直接引⽤:指针或者地址偏移量,引⽤对象⼀定在内存中。

  1. 初始化阶段:类初始化阶段是类加载过程的最后⼀步。初始化阶段就是执⾏类构造<clint>()⽅法的过程。
  2. 使⽤阶段:
  3. 卸载阶段:

08 类加载器

java默认提供三个类加载器:
在这里插入图片描述

  1. BootStrap ClassLoader 启动ClassLoader(sun.boot.class.path):最顶层的加载类,主要加载jdk中的核⼼库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。BootstrapClassLoader不继承⾃ClassLoader,因为它不是⼀个普通的Java类,底层由C++编写,已嵌⼊到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核⼼类库后,并构造Extension ClassLoader和App ClassLoader类加载器。
  2. Extension ClassLoader 扩展ClassLoader(java.ext.dirs):扩展的类加载器,加载⽬录%JRE_HOME%\lib\ext⽬录下的jar包和class⽂件。还可以加载-D java.ext.dirs选项指定的⽬录。
  3. App ClassLoader 应⽤ClassLoader/系统ClassLoader(java.class.path):也称为SystemAppClass加载当前应⽤的classpath的所有类。 除了BootStrap ClassLoader,每个ClassLoader都有⼀个Parent作为⽗亲。

双亲委派机制:

  1. 定义:当⼀个ClassLoader实例需要加载某个类时,它会试图亲⾃搜索某个类之前,先把这个任务委托给它的⽗类加载器,这个过程是由上⾄下依次检查的,⾸先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进⾏加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的⽂件系统或⽹络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类⽣成⼀个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。在这里插入图片描述
  2. 作⽤:避免重复加载;考虑到安全因素,避免⾃定义的类去替代系统类,如String。
  3. jvm如何判定两个class是否相同?JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,⽽且要判断是否由同⼀个类加载器实例加载的。只有两者同时满⾜的情况下,JVM才认为这两个class是相同的。
  1. ⾃底向上检查类是否已经加载;
  2. ⾃顶向下尝试加载类。

custom classloader:⾃定义classloader
a. Java中提供的默认ClassLoader,只加载指定⽬录下的jar和class,如果我们想加载其它位置的类或jar时,就需要定义⾃⼰的ClassLoader。
b. 步骤:

  1. 继承java.lang.ClassLoader
  2. 重写⽗类的findClass⽅法
    在这里插入图片描述

09 引起类加载的五个⾏为

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令
  2. 反射调⽤的时候,如果类没有进⾏过初始化,则需要先触发其初始化
  3. ⼦类初始化的时候,如果其⽗类还没初始化,则需先触发其⽗类的初始化
  4. 虚拟机执⾏主类的时候(有 main(string[] args))
  5. JDK1.7 动态语⾔⽀持

10 java对象创建时机

  1. 使⽤new关键字创建对象
  2. 使⽤Class类的newInstance⽅法(反射机制)
  3. 使⽤Constructor类的newInstance⽅法(反射机制)
  4. 使⽤Clone⽅法创建对象
  5. 使⽤(反)序列化机制创建对象

11 JVM调优

调优时机:

  • heap 内存(⽼年代)持续上涨达到设置的最⼤内存值;
  • Full GC 次数频繁;
  • GC 停顿时间过⻓(超过1秒);
  • 应⽤出现OutOfMemory 等内存异常;
  • 应⽤中有使⽤本地缓存且占⽤⼤量内存空间;
  • 系统吞吐量与响应性能不⾼或下降。

调优原则:

  • 多数的Java应⽤不需要在服务器上进⾏JVM优化;
  • 多数导致GC问题的Java应⽤,都不是因为我们参数设置错误,⽽是代码问题;
  • 在应⽤上线之前,先考虑将机器的JVM参数设置到最优(最适合);
  • 减少创建对象的数量;
  • 减少使⽤全局变量和⼤对象;
  • JVM优化是到最后不得已才采⽤的⼿段;
  • 在实际使⽤中,分析GC情况优化代码⽐优化JVM参数更好;

调优⽬标:

  • GC低停顿;
  • GC低频率;
  • 低内存占⽤;
  • ⾼吞吐量;

调优步骤:

  • 分析GC⽇志及dump⽂件,判断是否需要优化,确定瓶颈问题点;
  • 确定jvm调优量化⽬标;
  • 确定jvm调优参数(根据历史jvm参数来调整);
  • 调优⼀台服务器,对⽐观察调优前后的差异;
  • 不断的分析和调整,知道找到合适的jvm参数配置;
  • 找到最合适的参数,将这些参数应⽤到所有服务器,并进⾏后续跟踪。

12 jvm调优参数

  1. 设定堆内存⼤⼩,这是最基本的。
  2. -Xms:启动JVM时的堆内存空间。
  3. -Xmx:堆内存最⼤限制。
  4. 设定新⽣代⼤⼩。
  5. 新⽣代不宜太⼩,否则会有⼤量对象涌⼊⽼年代。
  6. -XX:NewRatio:新⽣代和⽼年代的占⽐。
  7. -XX:NewSize:新⽣代空间。
  8. -XX:SurvivorRatio:伊甸园空间和幸存者空间的占⽐。
  9. -XX:MaxTenuringThreshold:对象进⼊⽼年代的年龄阈值。
  10. 设定垃圾回收器
  • ----年轻代:-XX:+UseParNewGC。
  • ----⽼年代:-XX:+UseConcMarkSweepGC。
  • ----CMS可以将STW时间降到最低,但是不对内存进⾏压缩,有可能出现“并⾏模式失败”。⽐如⽼年代空间还有300MB空间,但是⼀些10MB的对象⽆法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要⽐Parallel GC⻓很多。
  • ----G1采⽤”标记-整理“算法,解决了内存碎⽚问题,建⽴了可预测的停顿时间类型,能让使⽤者指定在⼀个⻓度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。

13 触发full gc的场景及应对策略

  1. System.gc()⽅法的调⽤,应对策略:通过-XX:+DisableExplicitGC来禁⽌调⽤System.gc ;
  2. ⽼年代代空间不⾜,应对策略:让对象在Minor GC阶段被回收,让对象在新⽣代多存活⼀段时间,不要创建过⼤的对象及数组;
  3. 永⽣区空间不⾜,应对策略:增⼤PermGen空间;
  4. GC时出现promotionfailed和concurrent mode failure,应对策略:增⼤survivor space;
  5. Minor GC后晋升到旧⽣代的对象⼤⼩⼤于⽼年代的剩余空间,应对策略:增⼤Tenured space 或下CMSInitiatingOccupancyFraction=60;
  6. 内存持续增涨达到上限导致Full GC,应对策略:通过dumpheap 分析是否存在内存泄漏。

14 jvm堆的基本结构

在这里插入图片描述

  1. JVM中堆空间可以分成三个⼤区,新⽣代、⽼年代、永久代
  2. 新⽣代可以划分为三个区,Eden区,两个Survivor区,在HotSpot虚拟机Eden和Survivor的⼤⼩⽐例为8:1

15 如何查看jvm内存使⽤情况

可以使⽤JDK⾃带的JConsole、JVisualVM、JMap、JHat等⼯具,或者使⽤第三⽅⼯具,⽐如 Eclipse Memory Analyzer。

16 jvm内存溢出例⼦

  1. 内存溢出,⽐如给JVM分配的内存不够⼤,或者程序中存在死循环⼀直申请内存。
  2. 内存泄露,⽐如下⾯这段代码,list持有o的引⽤,o暂时是⽆法被JVM垃圾回收的,只有当list被垃圾回收或者o从对象list删除掉后,o才能被JVM垃圾回收。

在这里插入图片描述

17 常⽤的GC策略,什么时候会触发YGC,什么时候触发FGC?

YGC(Young GC):

  • 概念:对新⽣代堆进⾏GC。频率⽐较⾼,因为⼤部分对象的存活寿命较短,在新⽣代⾥被回收。性能耗费较⼩。
  • 触发时机:Eden区空间不⾜。

FGC(Full GC):

  • 概念:全堆范围的GC。默认堆空间使⽤到达80%(可调整)的时候会触发FGC。以我们⽣产环境为例,⼀般⽐较少会触发FGC,有时10天或⼀周左右会有⼀次。
  • 触发时机:
  • ------ Old空间不⾜;
  • ------ Perm空间不⾜;
  • ------ 显示调⽤System.gc() ,包括RMI等的定时触发;
  • ------ YGC时的悲观策略;
  • ------ dump live的内存信息时(jmap –dump:live)。

18 获得Class对象的方式

  1. 静态类的.class语法:GuideUtil.class
  2. 普通类对象的getClass()⽅法:new Test().getClass()
  3. 通过Class对象的forName()⽅法:
    Class.forName(“com.zhenai.modules.guide.utils.GuideUtil");
  4. 对于包装类,可以通过.TYPE语法⽅式:Integer.TYPE

19 内存溢出的可能原因和解决⽅法

  1. 数据加载过多,如1次从数据库中取出过多数据。
  2. 集合类中有对对象的引⽤,⽤完后没有清空或者集合对象未置空导致引⽤存在等,是的JVM⽆法回收。
  3. 死循环,过多重复对象。
  4. 第三⽅软件的bug。
  5. 启动参数内存值设定的过⼩。

解决⽅法:修改JVM启动参数,加内存(-Xms,-Xmx);错误⽇志,是否还有其他错误;代码⾛查。

20 内存泄漏的原因

  1. 未对作废数据内存单元置为null,尽早释放⽆⽤对象的引⽤,使⽤临时变量时,让引⽤变量在推出活动域后⾃动设置为null。

垃圾收集器收集;

  1. 程序避免⽤String拼接,⽤StringBuffer,因为每个String会占⽤内存⼀块区域;
  2. 尽量少⽤静态变量(全局不会回收);
  3. 不要集中创建对象尤其⼤对象,可以使⽤流操作;
  4. 尽量使⽤对象池,不再循环中创建对象,优化配置;
  5. 创建对象到单例getInstance中,对象⽆法回收被单例引⽤;
  6. 服务器session时间设置过⻓也会引起内存泄漏。

21 ⽅法区oom

  1. ⽅法区⽤于存放Class的相关信息,如:类名,访问修饰符,常量池,字符描述,⽅法描述等。
  2. 原因:运⾏时产⽣⼤量的类去填满⽅法区,直到溢出。

22 哪些情况下对象会进⼊⽼年代?

  1. 新⽣代对象每次经历⼀次minor gc,年龄会加1,当达到年龄阈值(默认为15岁)会直接进⼊⽼年代;
  2. ⼤对象直接进⼊⽼年代;
  3. 新⽣代复制算法需要⼀个survivor区进⾏轮换备份,如果出现⼤量对象在minor gc后仍然存活的情况时,就需要⽼年代进⾏分配担保,让survivor⽆法容纳的对象直接进⼊⽼年代;
  4. 如果在Survivor空间中相同年龄所有对象⼤⼩的总和⼤于Survivor空间的⼀半,年龄⼤于或等于该年龄的对象就可以直接进⼊年⽼代。

23 jvm中哪些地⽅会出现oom?分别说说oom的可能原因?

java堆溢出(heap):

  • Java堆内存主要⽤来存放运⾏过程中所以的对象,该区域OOM异常⼀般会有如下错误信息: java.lang.OutofMemoryError:Java heap space
  • 此类错误⼀般通过Eclipse Memory Analyzer分析OOM时dump的内存快照就能分析出来,到底是由于程序原因导致的内存泄露,还是由于没有估计好JVM内存的⼤⼩⽽导致的内存溢出。
  • 另外,Java堆常⽤的JVM参数:

-Xms:初始堆⼤⼩,默认值为物理内存的1/64(<1GB),默认(MinHeapFreeRatio参数可以调整)空余堆内存⼩于40%时,JVM就会增⼤堆直到
-Xmx:最⼤堆⼤⼩,默认值为物理内存的1/4(<1GB),默认(MaxHeapFreeRatio参数可以调整)空余堆内存⼤于70%时,JVM会减少堆直到
-Xmn:年轻代⼤⼩(1.4or lator),此处的⼤⼩是(eden + 2 survivor space),与jmap -heap中显示的New gen是不同的。

栈溢出(stack):

  • 栈⽤来存储线程的局部变量表、操作数栈、动态链接、⽅法出⼝等信息。如果请求栈的深度不⾜时抛出的错误会包含类似下⾯的信息:java.lang.StackOverflowError。
  • 另外,由于每个线程占的内存⼤概为1M,因此线程的创建也需要内存空间。操作系统可⽤内存-Xmx-MaxPermSize即是栈可⽤的内存,如果申请创建的线程⽐较多超过剩余内存的时候,也会抛出如下类似错误:java.lang.OutofMemoryError: unable to create new native thread
  • 相关的JVM参数有:

-Xss: 每个线程的堆栈⼤⼩,JDK5.0以后每个线程堆栈⼤⼩为1M,以前每个线程堆栈⼤⼩为256K.
在相同物理内存下,减⼩这个值能⽣成更多的线程.但是操作系统对⼀个进程内的线程数还是有限制的,不能⽆限⽣成,经验值在3000~5000

  • 可能原因:

递归:递归⾥⽤到的局部变量存储在堆栈中,堆栈的访问效率⾼,速度快,但空间有限,递归太多变量需要⼀直⼊栈⽽不出栈,导致需要的内存空间⼤于堆栈的空间,栈空间是2M,堆空间内存空间。

运⾏时常量溢出(constant):

  • 运⾏时常量保存在⽅法区,存放的主要是编译器⽣成的各种字⾯量和符号引⽤,但是运⾏期间也可能将新的常量放⼊池中,⽐如String类的intern⽅法。如果该区域OOM,错误结果会包含类似下⾯的信息:java.lang.OutofMemoryError: PermGen space
  • 相关的JVM参数有:

-XX:PermSize:设置持久代(perm gen)初始值,默认值为物理内存的1/64
-XX:MaxPermSize:设置持久代最⼤值,默认为物理内存的1/4

⽅法区溢出:

  • ⽅法区主要存储被虚拟机加载的类信息,如类名、访问修饰符、常量池、字段描述、⽅法描述等。理论上在JVM启动后该区域⼤⼩应该⽐较稳定,但是⽬前很多框架,⽐如Spring和Hibernate等在运⾏过程中都会动态⽣成类,因此也存在OOM的⻛险。如果该区域OOM,错误结果会包含类似下⾯的信息:java.lang.OutofMemoryError: PermGen space
  • 相关的JVM参数有:

-XX:PermSize:设置持久代(perm gen)初始值,默认值为物理内存的1/64
-XX:MaxPermSize:设置持久代最⼤值,默认为物理内存的1/4

24 如何定位jvm内存信息?

方法一:打印⽇志

-XX:+PrintGC:输出形式: 
[GC 118250K->113543K(130112K), 0.0094143 secs] 
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails:输出形式: 
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] 
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs
-XX:+PrintGCTimeStamps:打印GC停顿耗时
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间. 
-XX:+PrintHeapAtGC:打印GC前后的详细堆栈信息
-Xloggc:filename:把相关⽇志信息记录到⽂件以便分析.

方法二:错误调试

-XX:ErrorFile=./hs_err_pid<pid>.log:如果JVM crashed,将错误⽇志输出到指定⽂件路径。
-XX:HeapDumpPath=./java_pid<pid>.hprof:堆内存快照的存储⽂件路径。
-XX:-HeapDumpOnOutOfMemoryError:在OOM时,输出⼀个dump.core⽂件,记录当时的堆内存快照

方法三:类状态器相关

-XX:-TraceClassLoading:打印class装载信息到stdout。记Loaded状态。
-XX:-TraceClassUnloading:打印class的卸载信息到stdout。记Unloaded状态。

25 当对象A创建之后,对象A在各个区之间的流转过程

jvm堆结构图:

  • 在这里插入图片描述
  • 新⽣代通常占JVM堆内存的1/3,因为新⽣代存储都是新创建的对象,⽐较⼩的对象,⽽⽼年代存的都是⽐较⼤的,活的久的对象,所以⽼年代占JVM堆内存较⼤;
  • 新⽣代⾥的Eden区通常占年轻代的4/5,两个Survivor分别占新⽣代的1/10。因为Survivor中存储的是GC之后幸存的对象,实际上只有很少⼀部分会幸存,所以Survivor占的⽐例⽐较⼩。

对象流转流程(新⽣代复制算法,可以减少内存碎⽚)

  • 对象A被new出来之后,是被存放在Eden(伊甸园)区的。
  • 当发⽣⼀次GC后,Eden区存活下来的对象A会被复制到s1区,s0中存活的对象也会被复制到s1中。

如果对象年龄超过阈值年龄(默认15岁),会被复制到⽼年区。部分对象也需要⽼年代分担。

  • GC会清空Eden和s0中存储的所有对象;
  • 交换s0和s1的⻆⾊;
  • 重复上⾯的步骤。

26 jvm堆持久代

⽤于存放静态类型数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响。但是有些应⽤可能动态⽣成或调⽤⼀些Class,例如 Hibernate CGLib 等,在这种时候往往需要设置⼀个⽐较⼤的持久代空间来存放这些运⾏过程中动态增加的类型。

27 synchronized和ReentrantLock

synchronized⽤的锁是存在Java对象头⾥的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章