JVM总结

发布时间:2026/7/10 12:28:06
JVM总结 JVM1.JVM总结2.JVM结构3.JVM堆内存4.垃圾收集器和垃圾收集算法5.MinorGCMajorGCFullGC和YoungGC在堆中的发生区域6.如何查看jvm内存使用情况7.JVM加载类的机制8.什么时候触发YGC什么时候触发FGC?9.JVM的双亲委派机制10.哪些情况下对象会进入老年代?11. 为什么GC分代年龄最大为1512. JVM指令重排12.1 JVM指令重排的概念12.2 JVM指令重排带来的问题12.3 如何解决 多线程环境下的JVM指令重排 带来的问题13. java的虚引用有什么用1.JVM总结2.JVM结构1程序计数器线程私有无GCGarbage Collection指向当前线程正在执行的字节码的地址、行号。2虚拟机栈线程私有无GC存储当前线程运行方法所需要的数据、指令、返回地址。每一个方法都对应一个栈帧可以通过配置Xss来配置栈帧的大小。3本地方法栈线程私有无GC和虚拟机栈类似。不同的是本地方法栈存储的是本地方法的数据。4方法区线程共享被所有方法线程共享的一块内存区域。用于存储已经被虚拟机加载的类信息、常量、静态变量等。这个区域的内存回收目标主要针对常量池的回收和堆类型的卸载。5Java 堆线程共享要GC被所有线程共享的一块内存区域在虚拟机启动的时候创建用于存放对象实例。一个JVM实例只有一个堆内存。3.JVM堆内存JVM中堆空间可以分成三个大区新生代、老年代、永久代。新生代可以划分为三个区 Eden区两个Survivor区SurvivorFrom、SurvivorTo。Minor GC 的过程采用复制算法。首先把 Eden 和 SurvivorFrom 区域中存活的对象复制到 SurvivorTo 区域如果有对象的年龄以及达到了老年的标准一般是 15则赋值到老年代区同时把这些对象的年龄 1如果 SurvivorTo 不够位置了就放到老年区。然后清空 Eden 和 SurvivorFrom 中的对象最后SurvivorTo 和 SurvivorFrom 互换原 SurvivorTo 成为下一次 GC 时的 SurvivorFrom 区。SurvivorTo和SurvivorFrom互换主要有以下原因保证内存连续性新生代通常采用复制算法进行垃圾回收将Eden区和Survivor From区中存活的对象复制到Survivor To区然后清空Eden区和Survivor From区。通过这种方式可以将存活对象集中存放在一个Survivor区保证内存的连续性减少内存碎片的产生提高内存分配的效率2。实现对象分代对象在Survivor区中经历多次垃圾回收后如果仍然存活其年龄会不断增加。当对象的年龄达到一定阈值由JVM参数MaxTenuringThreshold决定默认是15时会被晋升到老年代。SurvivorTo和SurvivorFrom互换机制有助于记录对象的年龄实现对象的分代管理使得不同生命周期的对象存放在不同的内存区域提高垃圾回收的效率1。提高垃圾回收效率每次垃圾回收时只需要扫描Survivor From区和Eden区将存活对象复制到Survivor To区避免了对整个新生代的扫描减少了垃圾回收的时间和开销。同时由于Survivor区相对较小复制操作的成本也较低从而提高了垃圾回收的效率2。4.垃圾收集器和垃圾收集算法1引用计数法(没有被java采⽤)a. 原理理对于一个对象A只要有任何一个对象引⽤用了了A则A的引用计数器器就加1当引用失效时引用计数器器就减1只要对象A的引⽤用计数器器的值为0则对象A就会被回收。b. 问题i. 引⽤用和去引⽤用伴随加法和减法影响性能ii. 很难处理理循环引⽤用。2 标记清除法a. 原理 现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段标记阶段和清除阶段。一种可行的实现是在标记节点首先通过根节点标记所有从根节点开始的可达对象。因此未被标记的对象就是未被引用的垃圾对象。然后在清除阶段清除所有未被标记的对象。b. 问题i. 标记和清除两个过程效率不高产生内存碎片导致需要分配较大对象时无法找到足够的连续内存而需要触发一次GC操作。3标记压缩法a. 原理 适合用于存活对象较多的场合如老代。它在标记-清除算法的基础上做了一些优化。标记阶段一样但之后将所有存活对象压缩到内存的一端。之后清除边界外所有的空间。b. 优点i. 解决了标记- 清除算法导致的内存碎片问题和在存活率较高时复制算法效率低的问题。4.复制算法a. 原理将原有的内存空间分为两块每次只使用其中一块在垃圾回收时将正在使用的内存中的存活对象复制到未使用的内存块中之后清除正在使用的内存块中的所有对象交换两个内存的角色完成垃圾回收。b. 问题i. 不适用于存活对象⽐较多的场合如老年代。5分代回收法a. 原理根据对象存活周期的不不同将内存划分为几块一般是新生代和老年代 新生代基本采用复制算法老年代采用标记整理算法。5.MinorGCMajorGCFullGC和YoungGC在堆中的发生区域1.Minor GC通常发生在新生代的Eden区在这个区的对象生存期短往往发生GC的频率较高回收速度比较快一般采用复制-回收算法。2.Major GC 发⽣在老年代一般情况下触发老年代GC的时候不会触发Minor GC所采⽤的是标记-压缩算法。Minor GC / YoungGC 清理年轻代Major GC 清理老年代Full GC 清理整个堆空间包括年轻代和永久代6.如何查看jvm内存使用情况可以使用JDK自带的JConsole、 JVisualVM、 JMap、 JHat等工具或者使用第三方工具比如Eclipse Memory Analyzer。7.JVM加载类的机制1.概念JVM把描述类的数据文件字节码加载到内存并对数据进行验证、准备、解析以及类初始化最终形成可以被虚拟机直接使用的java类型Java.lang.Class对象)。2.类加载过程a.加载过程通过一个类的全限定名来获取定义此类的二进制字节流将这个字节流所代表的静态存储结构转化为方法区的运行时结构。在内存中(方法区)生成这个类的Class对象,作为方法区这个类的访问入口。b.验证过程为了确保Class文件的字节流中包含的信息符合当前JVM要求,进行文件格式验证元数据验证字节码验证符号引用验证。c.准备过程正式为类属性分配内存并设置类属性初始值的阶段这些内存都将在方法区中进行分配。准备阶段,static对象会被设置默认值,static final对象会被赋上给定的值d.解析过程虚拟机将常量池内的符号引用替换为直接引用的过程。i. 符号引用字符串引用对象不一定被加载ii. 直接引用指针或者地址偏移量引用对象一定在内存中。e.初始化阶段类初始化阶段是类加载过程的最后一步,是调用类的构造器过程。f.使用阶段g.卸载阶段8.什么时候触发YGC什么时候触发FGC?1.YGC(Young GC)a. 概念对新生代堆进行GC。频率⽐较高因为大部分对象的存活寿命较短在新生代里被回收。性能耗费较小。b. 触发时机 新生代的Eden区空间不足。2.FGC(Full GC)a.概念全堆范围的GC。 默认堆空间使用到达80%(可调整)的时候会触发FGC。以我们生产环境为例一般比较少会触发FGC有时10天或一周左右会有一次。b. 触发时机1、 老年代空间不⾜2、 永久代空间不足3、显示调⽤用System.gc() ;9.JVM的双亲委派机制a.定义当一个类加载器实例需要加载某个类时它会在亲自搜索这个类之前先把这个任务委托给它的父类加载器如果父类加载器还存在父类加载器就继续向上委托直到顶层的启动类加载器.首先由最顶层的类加载器BootStrapClassLoader(启动类加载器)试图加载如果没有加载到则把任务转交给ExtensionClassLoader(扩展类加载器)试图加载如果没有加载到则转交给AppClassLoader(应用类加载器)进行加载如果它也没有加载得到的话则返回给委托的发起者由它到指定的文件系统或网络等URL中加载该类。如果都没有加载得到的话就抛出ClassNotFoundException异常。如果加载成功就会生成一个Class实例对象在内存中。b.双亲委派机制的作用是什么?1.避免重复加载同一个类。通过委托去向上面问一问加载过了就不用再加载一遍。保证数据安全。2.避免自定义的类去替代系统核心类造成安全问题如String。10.哪些情况下对象会进入老年代?1.新生代对象每经历一次minorGC,分代年龄就会加1,当年龄达到阀值15岁的时候会直接进入老年代.2.大对象直接进入老年代.3.如果出现大量对象在minorGC后仍然存活的情况时,就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代.11. 为什么GC分代年龄最大为15存储方面 对象的对象头里面有4个bit位来存储GC年龄,二进制的4位 最大值位1111 十进制的 15设计方面参考12. JVM指令重排12.1 JVM指令重排的概念CPU和编译器为了提升程序执行效率会按照一定的规则去调整JVM指令的执行顺序从而达到优化效果。12.2 JVM指令重排带来的问题JVM指令重排在单线程环境下是不会影响最终结果的但是在多线程环境下可能会影响到最终结果。以下 r.score 可能出现的结果为 410。情况1线程2 执行完 m2方法线程1 再执行 m1方法r.score 等于 4。情况2线程1 执行完 m1方法线程2 再执行 m2方法r.score 等于 1。情况3m2() 发生了JVM指令重排把B1和B2代码互换了B2在B1前面线程2执行完了m2-B2代码行接着线程1执行m1-A1 m1-A2最后线程2再执行m2-B1最终r.score 等于0。// 线程2 执行此方法 public void m2() { ready true;// B2 num 2;// B1 }publicclassMAIN{privateintnum0;privatebooleanreadyfalse;publicstaticclassStudent{privateintscore;}// 线程1 执行此方法publicvoidm1(Studentr){if(ready){// A1r.scorenumnum;// A2}else{r.score1;// A3}System.out.println(r r.score);}// 线程2 执行此方法publicvoidm2(){num2;// B1readytrue;// B2}publicstaticvoidmain(String[]args)throwsInterruptedException{MAINmainnewMAIN();StudentstudentnewStudent();CountDownLatchcountDownLatchnewCountDownLatch(1);newThread(()-{try{countDownLatch.await();System.out.println(XC2_主线程放行!!);main.m2();}catch(InterruptedExceptione){e.printStackTrace();}}).start();newThread(()-{try{countDownLatch.await();System.out.println(XC1_主线程放行!!);main.m1(student);}catch(InterruptedExceptione){e.printStackTrace();}}).start();// 主线程休眠3秒TimeUnit.SECONDS.sleep(3);// 计数器减一 让2个线程并发执行countDownLatch.countDown();}}使用CountDownLatch 模拟2个线程并发执行控制台打印可能出现的结果XC1_主线程放行!!XC2_主线程放行!!r1XC2_主线程放行!!XC1_主线程放行!!r4XC2_主线程放行!!XC1_主线程放行!!r012.3 如何解决 多线程环境下的JVM指令重排 带来的问题使用volatile关键字解决volatile可以局部阻止重排序的发生对volatile变量的操作指令都不会被重排序。JVM将内存组织为主内存和工作内存两个部分。主内存主要包括本地方法区和堆。每个线程都有一个工作内存工作内存中主要包括两个部分一个是属于该线程私有的栈和对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓存区)。1所有的成员变量都存储在主内存中(堆中)对于所有线程都是共享的。2每条线程都有自己的工作内存工作内存中保存的是主存中某些变量的拷贝线程对变量的所有操作都必须在工作内存中进行而不能直接读写主内存中的变量。3线程之间无法直接访问对方的工作内存中的变量线程间变量的传递均需要通过主内存来完成。任何被volatile修饰的变量都不拷贝副本到工作内存任何修改都及时写在主存。因此对于volatile修饰的变量的修改所有线程马上就能看到。13. java的虚引用有什么用虚引用作用虚引用 和 引用队列 配合使用 跟踪对象被GC回收的活动。通过判断引用队列中是否有虚引用来了解被引用对象是否被回收。// 声明 强引用StudentstnewStudent();// 声明 关联被回收对象的 引用队列 ⇒ 当对象被GC回收后 会进入 引用队列ReferenceQueueObjectreferenceQueuenewReferenceQueue();// 声明 虚引用PhantomReferenceObjectreferencenewPhantomReference(st,referenceQueue);stnull;// help GCSystem.gc();// 显式GC对象st被回收后 会进入引用队列 referenceQueue中System.out.println(referenceQueue referenceQueue.poll());Student对象被回收后虚引用 进入了引用队列