建立自己的网站广州注销营业执照

张小明 2026/1/12 16:46:27
建立自己的网站,广州注销营业执照,宁国市网站建设,网站开发费用计入什么科目#x1f9d1; 博主简介#xff1a;CSDN博客专家#xff0c;历代文学网#xff08;PC端可以访问#xff1a;https://literature.sinhy.com/#/literature?__c1000#xff0c;移动端可微信小程序搜索“历代文学”#xff09;总架构师#xff0c;15年工作经验#xff0c;… 博主简介CSDN博客专家历代文学网PC端可以访问https://literature.sinhy.com/#/literature?__c1000移动端可微信小程序搜索“历代文学”总架构师15年工作经验精通Java编程高并发设计Springboot和微服务熟悉LinuxESXI虚拟化以及云原生Docker和K8s热衷于探索科技的边界并将理论知识转化为实际应用。保持对新技术的好奇心乐于分享所学希望通过我的实践经历和见解启发他人的创新思维。在这里我希望能与志同道合的朋友交流探讨共同进步一起在技术的世界里不断学习成长。技术合作请加本人wx注明来自csdnforeast_sea文章目录JVM 核心知识点总结一、基本概念1.1 OpenJDK1.2 OracleJDK1.3 HotSpot VM二、Java 内存区域2.1 程序计数器2.2 虚拟机栈2.3 本地方法栈2.4 堆2.5 方法区三、对象3.1 对象的创建1. 类加载过程2. 分配内存3. 对象头设置4. 对象初始化3.2 对象的内存布局1. 对象头 (Header)2. 实例数据 (Instance Data)3. 对齐填充 (Padding)3.3 对象的访问定位四、垃圾收集机制1. 引用计数法2. 可达性分析3. 对象引用强引用 (Strongly Reference)软引用 (Soft Reference)弱引用 (Weak Reference)虚引用 (Phantom Reference)4. 对象真正死亡4.3 垃圾收集算法1. 分代收集理论2. 回收类型3. 标记-清除算法4. 标记-复制算法5. 标记-整理算法五、垃圾收集器5.1 Serial 收集器5.2 ParNew 收集器5.3 Parallel Scavenge 收集器5.4 Serial Old 收集器5.5 Paralled Old 收集器5.6 CMS 收集器5.7 Garbage First 收集器5.8 内存分配原则1. 对象优先在 Eden 分配2. 大对象直接进入老年代3. 长期存活的对象将进入老年代4. 动态年龄判断5. 空间担保分配六、类加载机制6.1 类加载时机6.2 类加载过程1. 加载2. 验证3. 准备4. 解析5. 初始化6.3 类加载器6.4 双亲委派模型6.5 模块化下的类加载器七、程序编译7.1 编译器分类7.2 解释器与编译器7.3 分层编译7.4 热点探测八、代码优化8.1 方法内联8.2 逃逸分析8.3 公共子表达式消除8.4 数组边界检查消除小结JVM 核心知识点总结JVM 核心知识点总结包括基本概念、Java 内存区域、对象、垃圾收集机制、类加载机制、字节码、JIT、JVM 调优等。一、基本概念1.1 OpenJDK自 1996 年JDK 1.0发布以来Sun 公司在大版本上发行了JDK 1.1、JDK 1.2、JDK 1.3、JDK 1.4、JDK 5JDK 6这些版本的 JDK 都可以统称为 SunJDK 。之后在 2006 年的 JavaOne 大会上Sun 公司宣布将 Java 开源在随后的一年多里它陆续将 JDK 的各个部分在 GPL v2GNU General Public Licenseversion 2协议下开源并建立了 OpenJDK 组织来对这些代码进行独立的管理这就是 OpenJDK 的来源此时的 OpenJDK 拥有当时 sunJDK 7 的几乎全部代码。1.2 OracleJDK在JDK 7的开发期间由于各种原因的影响Sun 公司市值一路下跌已无力推进JDK 7的开发于是JDK 7的发布一直被推迟。之后在 2009 年 Sun 公司被Oracle公司收购为解决JDK 7长期跳票的问题Oracle 将JDK 7中大部分未能完成的项目推迟到JDK 8并于 2011 年发布了JDK 7在这之后由 Oracle 公司正常发行的 JDK 版本就由 SunJDK 改称为Oracle JDK。在 2017 年 JDK 9 发布后Oracle 公司宣布以后 JDK 将会在每年的 3 月和 9 月各发布一个大版本即半年发行一个大版本目的是为了避免众多功能被捆绑到一个 JDK 版本上而引发的无法交付的风险。在 JDK 11 发布后Oracle 同步调整了 JDK 的商业授权宣布从 JDK 11 起将以前的商业特性全部开源给 OpenJDK 这样 OpenJDK 11 和 OracleJDK 11 的代码和功能在本质上就完全相同了。同时还宣布以后会发行两个版本的 JDK 一个是在 GPLv2 CE 协议下由 Oracle 开源的 OpenJDK一个是在 OTN 协议下正常发行的 OracleJDK。两者共享大部分源码在功能上几乎一致。唯一的区别是 Oracle OpenJDK 可以在开发、测试或者生产环境中使用但只有半年的更新支持而 OracleJDK 对个人免费但在生产环境中商用收费可以有三年时间的更新支持。目前最新的长期支持的 JDK 是JDK 21LTS。1.3 HotSpot VM它是Sun/Oracle JDK和OpenJDK中默认的虚拟机也是目前使用最为广泛的虚拟机。最初由Longview Technologies公司设计发明该公司在 1997 年被 Sun 公司收购随后 Sun 公司在 2006 年开源SunJDK时也将 HotSpot 虚拟机一并进行了开源。Oracle收购 Sun 以后建立了 HotRockit 项目并将其收购的另外一家公司BEA的 JRockit 虚拟机中的优秀特性集成到 HotSpot 中。HotSpot在这个过程里移除掉永久代并吸收了JRockit的Java Mission Control监控工具等功能。到JDK 8发行时采用的就是集两者之长的HotSpot VM。我们可以在自己的电脑上使用java -version来获得 JDK 的信息二、Java 内存区域Java 内存区域我们之前讲过这里再盘一盘。2.1 程序计数器程序计数器Program Counter Register是一块较小的内存空间它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令分支、循环、跳转、异常处理、线程恢复等基础功能都需要该计数器来完成。每个线程都拥有一个独立的程序计数器各个线程之间的计数器互不影响独立存储。2.2 虚拟机栈虚拟机栈Java Virtual Machine Stack也是线程私有它描述的是 Java 方法执行的线程内存模型每个方法被执行的时候Java 虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。方法从调用到结束就对应着一个栈帧从入栈到出栈的过程。在《Java 虚拟机规范》中对该内存区域规定了两类异常如果线程请求的栈深度大于虚拟机所允许的栈深度将抛出StackOverflowError异常如果 Java 虚拟机栈的容量允许动态扩展当栈扩展时如果无法申请到足够的内存会抛出OutOfMemoryError异常。2.3 本地方法栈本地方法栈Native Method Stacks与虚拟机栈类似其区别在于Java 虚拟机栈是为虚拟机执行 Java 方法也就是字节码服务的而本地方法栈则是为 JVM 使用到的本地Native方法服务。2.4 堆堆Java Heap是虚拟机所管理的最大一块内存空间它被所有线程所共享用于存放对象实例。Java 堆可以处于物理上不连续的内存空间中但在逻辑上它应该被视为是连续的。Java 堆可以被实现成固定大小的也可以是可扩展的。当前大多数主流的虚拟机都是按照可扩展来实现的即可以通过最大值参数-Xmx和最小值参数-Xms进行设定。如果 Java 堆中没有足够的内存来完成对象实例分配并且堆也无法再扩展时Java 虚拟机将会抛出OutOfMemoryError异常。2.5 方法区方法区Method Area也是各个线程共享的内存区域用于存储已被虚拟机加载的类信息、常量、静态变量、JIT 编译后的代码缓存等数据。方法区也被称为 “非堆”目的是与 Java 堆进行区分。《Java 虚拟机规范》规定如果方法区无法满足新的内存分配需求时将会抛出OutOfMemoryError异常。JDK 8 以后的方法区实现已经不再是永久代Permanent Generation了而是使用元空间Metaspace来实现。运行时常量池Runtime Constant Pool也是方法区的一部分用于存放常量池表Constant Pool Table常量池表中存放了编译期生成的各种符号字面量和符号引用。JDK 8 以后的运行时常量池在元空间中。三、对象3.1 对象的创建当我们在代码中使用new关键字创建一个对象时其在 JVM 中需要经过以下步骤1. 类加载过程当虚拟机遇到一条字节码指令new时首先将去检查这个指令的参数是否能在常量池中定位到一个符号引用并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有就必须先执行相应的类加载过程。2. 分配内存在类加载检查通过后虚拟机需要给新生对象分配内存空间。根据 Java 堆是否规整可以有以下两种分配方案①、指针碰撞假设 Java 堆中内存是绝对规整的所有使用的内存放在一边所有未被使用的内存放在另外一边中间以指针作为分界点指示器。此时内存分配只是将指针向空闲方向偏移出对象大小的空间即可这种方式被称为指针碰撞。②、空闲列表如果 Java 堆不是规整的此时虚拟机需要维护一个列表记录哪些内存块是可用的哪些是不可用的。在进行内存分配时只需要从该列表中选取出一块足够的内存空间划分给对象实例即可。注Java 堆是否规整取决于其采用的垃圾收集器是否带有空间压缩整理能力前面讲过了。除了分配方式外由于对象创建在虚拟机中是一个非常频繁的行为此时需要保证在并发环境下的线程安全如果一个线程给对象 A 分配了内存空间但指针还没来得及修改此时就可能出现另外一个线程使用原来的指针来给对象 B 分配内存空间的情况。想要解决这个问题有两个方案①、方式一采用同步锁定或采用 CAS 配上失败重试的方式来保证更新操作的原子性。②、方式二为每个线程在 Java 堆中预先分配一块内存称为本地线程分配缓冲Thread Local Allocation BufferTLAB。线程在进行内存分配时优先使用本地缓冲当本地缓冲使用完成后再向 Java 堆申请分配此时 Java 堆采用同步锁定的方式来保证分配行为的线程安全。3. 对象头设置将对象有关的元数据信息、对象的哈希码、分代年龄等信息存储到对象头中。可以和 JIT 那节的内容关联起来。4. 对象初始化调用对象的构造方法即 Class 文件中的init()来初始化对象为相关字段赋值。3.2 对象的内存布局在 HotSpot 中对象在堆内存中的存储布局可以划分为以下三个部分1. 对象头 (Header)对象头包括两部分信息Mark Word对象自身的运行时数据如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等官方统称为 Mark Word我们曾在 synchronized 的四种锁状态讲过。类型指针对象指向它类型元数据的指针Java 虚拟机通过这个指针来确定该对象是哪个类的实例。需要说明的是并非所有的虚拟机都必须要在对象数据上保留类型指针这取决于对象的访问定位方式。2. 实例数据 (Instance Data)即我们在代码中定义的各种类型的字段无论是从父类继承而来还是子类中定义的都需要记录。3. 对齐填充 (Padding)主要起占位符的作用。HotSpot 要求对象起始地址必须是 8 字节的整倍数即间接要求了任何对象的大小都必须是 8 字节的整倍数。对象头部分在设计上就是 8 字节的整倍数如果对象的实例数据不是 8 字节的整倍数则由对齐填充进行补全。3.3 对象的访问定位对象创建后Java 程序就可以通过栈上的reference也就是引用来操作堆上的具体对象。《Java 虚拟机规范》规定reference是一个指向对象的引用但并未规定其具体实现方式。主流的方式方式有以下两种句柄访问Java 堆将划分出一块内存来作为句柄池reference中存储的是对象的句柄地址而句柄则包含了对象实例数据和类型数据的地址信息。指针访问reference中存储的直接就是对象地址而对象的类型数据则由上文介绍的对象头中的类型指针来指定。通过句柄访问对象通过直接指针访问对象句柄访问的优点在于对象移动时垃圾收集时移动对象是非常普遍的行为只需要改变句柄中实例数据的指针而reference本身并不需要修改指针访问则反之由于其reference中存储的直接就是对象地址所以当对象移动时reference需要被修改。但针对只需要访问对象本身的场景指针访问则可以减少一次定位开销。由于对象访问是一项非常频繁的操作所以这类减少的效果会非常显著基于这个原因HotSpot 主要使用的是指针访问的方式。四、垃圾收集机制在 JVM 内存模型中程序计数器、虚拟机栈、本地方法栈这 3 个区域都是线程私有的会随着线程的结束而销毁因此在这 3 个区域当中无需过多考虑垃圾回收问题。垃圾回收问题主要发生在 Java 堆上。在 Java 堆上垃圾回收的主要内容是死亡的对象不可能再被任何途径使用的对象。判断对象是否死亡有以下两种方法1. 引用计数法在对象中添加一个引用计数器对象每次被引用时该计数器加一当引用失效时计数器的值减一只要计数器的值为零则代表对应的对象不可能再被使用。该方法的缺点在于无法避免相互引用的问题objA.instanceobjB objB.instanceobjA objAnull;objBnull;System.gc();如上所示此时两个对象已经不能再被访问但其互相持有对对方的引用如果采用引用计数法则两个对象都无法被回收。2. 可达性分析但上面的代码在大多数虚拟机中都能被正确的回收因为大多数主流的虚拟机都是采用的可达性分析方法来判断对象是否死亡。可达性分析是通过一系列被称为GC Roots的根对象作为起始节点集从这些节点开始根据引用关系向下搜索搜索过程所走过的路径被称为引用链Reference Chain如果某个对象到GC Roots间没有任何引用链相连这代表GC Roots到该对象不可达 此时证明该对象不可能再被使用。在 Java 语言中固定可作为GC Roots的对象包括以下几种在虚拟机栈栈帧中的本地变量表中引用的对象譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等在方法区元空间中类静态变量引用的对象譬如 Java 类中引用类型的静态变量在方法区元空间中常量引用的对象譬如字符串常量池String Table里的引用在本地方法栈中的 JNINative 方法引用的对象Java 虚拟机内部的引用如基本数据类型对应的 Class 对象一些常驻的异常对象如 NullPointExceptionOutOfMemoryError 等及系统类加载器所有被同步锁synchronized 关键字持有的对象除了这些固定的GC Roots集合以外根据用户所选用的垃圾收集器以及当前回收的内存区域的不同还可能会有其他对象 “临时性” 地加入共同构成完整的GC Roots集合。3. 对象引用可达性分析是基于引用链进行判断的在 JDK 1.2 之后Java 将引用关系分为以下四类强引用 (Strongly Reference)最传统的引用如Object obj new Object()。无论任何情况下只要强引用关系还存在垃圾收集器就永远不会回收掉被引用的对象。软引用 (Soft Reference)用于描述一些还有用但非必须的对象。只被软引用关联着的对象在系统将要发生内存溢出之前会被列入回收范围内进行第二次回收如果这次回收后还没有足够的内存才会抛出内存溢出异常。下面是一个使用 Java 中 SoftReference 类的示例代码classSoftReferenceExample{publicstaticvoidmain(String[]args){// 创建一个强引用的对象StringstrongReferencenewString(二哥我是个强引用);// 创建一个软引用指向上面的对象SoftReferenceStringsoftReferencenewSoftReference(strongReference);// 干掉强引用strongReferencenull;// 现在只有软引用指向 二哥我是个强引用 对象// 尝试通过软引用获取对象StringretrievedStringsoftReference.get();System.out.println(retrievedString);// 输出 二哥我是个强引用// 强制进行垃圾回收可能会清除软引用的对象System.gc();// 再次尝试通过软引用获取对象retrievedStringsoftReference.get();if(retrievedString!null){System.out.println(retrievedString);}else{System.out.println(软引用的对象已被垃圾回收);}}}这个例子中我们首先创建了一个字符串对象的强引用然后通过 SoftReference 创建了这个对象的软引用。在取消了强引用后这个对象只剩下软引用。当我们尝试通过软引用获取对象时如果对象还存在软引用会返回它如果对象已被垃圾收集器回收则返回 null。需要注意的是第二次回收时如果这次回收后还没有足够的内存才会抛出内存溢出异常。这里的 “足够” 是指在抛出内存溢出异常之前系统会进行最后一次尝试如果这次回收后还是没有足够的内存才会抛出内存溢出异常。通常情况下上面代码在执行 gc 后软引用不会被回收因为此时内存还是足够的。二哥我是个强引用 二哥我是个强引用弱引用 (Weak Reference)用于描述那些非必须的对象强度比软引用弱。被弱引用关联的对象只能生存到下一次垃圾收集发生时无论当前内存是否足够弱引用对象都会被回收。来看这段代码classWeakReferenceExample{publicstaticvoidmain(String[]args){// 创建一个强引用的对象StringstrongReferencenewString(二哥我是强引用);// 创建一个弱引用指向上面的对象WeakReferenceStringweakReferencenewWeakReference(strongReference);// 取消强引用strongReferencenull;// 强制进行垃圾回收System.gc();// 尝试通过弱引用获取对象StringretrievedStringweakReference.get();if(retrievedString!null){System.out.println(retrievedString);}else{System.out.println(弱引用的对象已被垃圾回收);}}}这个例子中我们首先创建了一个字符串对象的强引用然后通过 WeakReference 创建了这个对象的弱引用。在取消了强引用后这个对象只剩下弱引用。当我们尝试通过弱引用获取对象时如果对象还存在弱引用会返回它如果对象已被垃圾收集器回收则返回 null。运行结果就和软引用不一样了gc 后弱引用被回收了。弱引用的对象已被垃圾回收虚引用 (Phantom Reference)最弱的引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被回收时收到一个系统通知。虚引用必须和引用队列ReferenceQueue联合使用。当垃圾收集器准备回收一个对象时如果发现它还有虚引用就会在回收对象后将这个虚引用加入到与之关联的引用队列中。来看这个示例代码classPhantomReferenceExample{publicstaticvoidmain(String[]args){// 创建一个强引用的对象StringstrongReferencenewString(二哥我是强引用);// 创建一个引用队列ReferenceQueueStringreferenceQueuenewReferenceQueue();// 创建一个虚引用指向上面的对象并与引用队列关联PhantomReferenceStringphantomReferencenewPhantomReference(strongReference,referenceQueue);// 取消强引用strongReferencenull;// 强制进行垃圾回收System.gc();// 检查引用队列看是否有通知if(referenceQueue.poll()!null){System.out.println(虚引用的对象已被垃圾回收且收到了通知);}else{System.out.println(虚引用的对象未被垃圾回收或未收到通知);}}}这个例子中我们首先创建了一个字符串对象的强引用然后通过 PhantomReference 创建了这个对象的虚引用并与引用队列关联。在取消了强引用后这个对象只剩下虚引用。当我们强制进行垃圾回收时如果对象还存在虚引用不会返回它如果对象已被垃圾收集器回收则返回 null。同时如果对象被回收虚引用会被加入到引用队列中。运行结果如下虚引用的对象已被垃圾回收且收到了通知4. 对象真正死亡要真正宣告一个对象死亡需要经过至少两次标记过程①、如果对象在进行可达性分析后发现GC Roots不可达将会进行第一次标记②、随后进行一次筛选筛选的条件是此对象是否有必要执行finalized()方法。如果对象没有覆盖finalized()方法或者finalized()已经被虚拟机调用过这两种情况都会视为没有必要执行。如果判定结果是有必要执行此时对象会被放入名为F-Queue的队列等待 Finalizer 线程执行其finalized()方法。在这个过程中收集器会进行第二次小规模的标记如果对象在finalized()方法中重新将自己与引用链上的任何一个对象进行了关联如将自己this 关键字赋值给某个类变量或者对象的成员变量此时它就实现了自我拯救则第二次标记会将其移除 “即将回收” 的集合否则该对象就将被真正回收走向死亡。4.3 垃圾收集算法1. 分代收集理论当前大多数虚拟机都遵循 “分代收集” 的理论进行设计它建立在强弱两个分代假说下弱分代假说 (Weak Generational Hypothesis)绝大多数对象都是朝生夕灭的。强分代假说 (Strong Generational Hypothesis)熬过越多次垃圾收集过程的对象就越难以消亡。跨带引用假说 (Intergenerational Reference Hypothesis)基于上面两条假说还可以得出的一条隐含推论存在相互引用关系的两个对象应该倾向于同时生存或者同时消亡。强弱分代假说奠定了垃圾收集器的设计原则收集器应该将 Java 堆划分出不同的区域然后将回收对象依据其年龄年龄就是对象经历垃圾收集的次数分配到不同的区域中进行存储。之后如果一个区域中的对象都是朝生夕灭的那么收集器只需要关注少量对象的存活而不是去标记那些大量将要被回收的对象此时就能以较小的代价获取较大的空间。最后再将难以消亡的对象集中到一块根据强分代假说它们是很难消亡的因此虚拟机可以使用较低的频率进行回收这就兼顾了时间和内存空间的开销。2. 回收类型根据分代收集理论收集范围可以分为以下几种类型①、部分收集 (Partial GC)具体分为新生代收集Minor GC / Young GC只对新生代进行垃圾收集老年代收集Major GC / Old GC只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集混合收集Mixed GC对整个新生代和部分老年代进行垃圾收集。整堆收集 (Full GC)收集整个 Java 堆和方法区。3. 标记-清除算法它是最基础的垃圾收集算法收集过程分为两个阶段首先标记出所有需要回收的对象在标记完成后统一回收掉所有被标记的对象也可以反过来标记存活对象统一回收所有未被标记的对象。它主要有以下两个缺点执行效率不稳定如果 Java 堆上包含大量需要回收的对象则需要进行大量标记和清除动作内存空间碎片化标记清除后会产生大量不连续的空间从而导致无法为大对象分配足够的连续内存。4. 标记-复制算法标记-复制算法基于 ”半区复制“ 算法它将可用内存按容量划分为大小相等的两块每次只使用其中一块当这一块的内存使用完了就将还存活着的对象复制到另外一块然后再把已经使用过的那块内存空间一次性清理掉。其优点在于避免了内存空间碎片化的问题其缺点如下如果内存中多数对象都是存活的这种算法将产生大量的复制开销浪费内存空间内存空间变为了原有的一半。基于新生代 “朝生夕灭” 的特点大多数虚拟机都不会按照 1:1 的比例来进行内存划分例如 HotSpot 会将内存空间划分为一块较大的Eden和两块较小的Survivor空间它们之间的比例是 8:1:1 。每次分配时只会使用Eden和其中的一块Survivor发生垃圾回收时只需要将存活的对象一次性复制到另外一块Survivor上这样只有 10% 的内存空间会被浪费掉。当Survivor空间不足以容纳一次Minor GC时此时由其他内存区域通常是老年代来进行分配担保。5. 标记-整理算法标记-整理算法是在标记完成后让所有存活对象都向内存的一端移动然后直接清理掉边界以外的内存。其优点在于可以避免内存空间碎片化的问题也可以充分利用内存空间其缺点在于根据所使用的收集器的不同在移动存活对象时可能要全程暂停用户程序五、垃圾收集器并行与并发是并发编程中的专有名词在谈论垃圾收集器的上下文语境中它们的含义如下①、并行 (Parallel)并行描述的是多条垃圾收集器线程之间的关系说明同一时间有多条这样的线程在协同工作此时通常默认用户线程是处于等待状态。②、并发 (Concurrent)并发描述的是垃圾收集器线程与用户线程之间的关系说明同一时间垃圾收集器线程与用户线程都在运行。但由于垃圾收集器线程会占用一部分系统资源所以程序的吞吐量依然会受到一定影响。HotSpot 中一共存在七款经典的垃圾收集器注收集器之间存在连线代表它们可以搭配使用。5.1 Serial 收集器Serial 收集器是最基础、历史最悠久的收集器它是一个单线程收集器在进行垃圾回收时必须暂停其他所有的工作线程直到收集结束这是其主要缺点。它的优点在于单线程避免了多线程复杂的上下文切换因此在单线程环境下收集效率非常高由于这个优点迄今为止其仍然是 HotSpot 虚拟机在客户端模式下默认的新生代收集器5.2 ParNew 收集器它是 Serial 收集器的多线程版本可以使用多条线程进行垃圾回收5.3 Parallel Scavenge 收集器Parallel Scavenge 也是新生代收集器基于 标记-复制 算法进行实现它的目标是达到一个可控的吞吐量。这里的吞吐量指的是处理器运行用户代码的时间与处理器总消耗时间的比值吞吐量 运行用户代码时间 \ (运行用户代码时间 运行垃圾收集时间)Parallel Scavenge 收集器提供两个参数用于精确控制吞吐量①、-XX:MaxGCPauseMillis控制最大垃圾收集时间假设需要回收的垃圾总量不变那么降低垃圾收集的时间就会导致收集频率变高所以需要将其设置为合适的值不能一味减小。②、-XX:MaxGCTimeRatio直接用于设置吞吐量大小它是一个大于 0 小于 100 的整数。假设把它设置为 19表示此时允许的最大垃圾收集时间占总时间的 5%即 1/(119) 默认值为 99 即允许最大 1% 1/(199) 的垃圾收集时间。5.4 Serial Old 收集器从名字也能看出来它是 Serial 收集器的老年代版本同样是一个单线程收集器采用 标记-整理 算法主要用于给客户端模式下的 HotSpot 使用5.5 Paralled Old 收集器Paralled Old 是 Parallel Scavenge 收集器的老年代版本支持多线程并发收集采用 标记-整理 算法实现5.6 CMS 收集器CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器基于 标记-清除 算法实现整个收集过程分为以下四个阶段初始标记 (inital mark)标记GC Roots能直接关联到的对象耗时短但需要暂停用户线程并发标记 (concurrent mark)从GC Roots能直接关联到的对象开始遍历整个对象图耗时长但不需要暂停用户线程重新标记 (remark)采用增量更新算法对并发标记阶段因为用户线程运行而产生变动的那部分对象进行重新标记耗时比初始标记稍长且需要暂停用户线程并发清除 (inital sweep)并发清除掉已经死亡的对象耗时长但不需要暂停用户线程。其优点在于耗时长的 并发标记 和 并发清除 阶段都不需要暂停用户线程因此其停顿时间较短其主要缺点如下由于涉及并发操作因此对处理器资源比较敏感。由于是基于 标记-清除 算法实现的因此会产生大量空间碎片。无法处理浮动垃圾Floating Garbage由于并发清除时用户线程还是在继续所以此时仍然会产生垃圾这些垃圾就被称为浮动垃圾只能等到下一次垃圾收集时再进行清理。5.7 Garbage First 收集器Garbage First简称 G1是一款面向服务端的垃圾收集器也是 JDK 9 服务端模式下默认的垃圾收集器它的诞生具有里程碑式的意义。G1 虽然也遵循分代收集理论但不再以固定大小和固定数量来划分分代区域而是把连续的 Java 堆划分为多个大小相等的独立区域Region。每一个 Region 都可以根据不同的需求来扮演新生代的Eden空间、Survivor空间或者老年代空间收集器会根据其扮演角色的不同而采用不同的收集策略。上面还有一些 Region 使用 H 进行标注它代表 Humongous表示这些 Region 用于存储大对象humongous objectH-obj即大小大于等于 region 一半的对象。G1 收集器的运行大致可以分为以下四个步骤①、初始标记 (Inital Marking)标记GC Roots能直接关联到的对象并且修改 TAMSTop at Mark Start指针的值让下一阶段用户线程并发运行时能够正确的在 Reigin 中分配新对象。G1 为每一个 Reigin 都设计了两个名为 TAMS 的指针新分配的对象必须位于这两个指针位置以上位于这两个指针位置以上的对象默认被隐式标记为存活的不会纳入回收范围②、并发标记 (Concurrent Marking)从GC Roots能直接关联到的对象开始遍历整个对象图。遍历完成后还需要处理 SATB 记录中变动的对象。SATBsnapshot-at-the-beginning开始阶段快照能够有效的解决并发标记阶段因为用户线程运行而导致的对象变动其效率比 CMS 重新标记阶段所使用的增量更新算法效率更高③、最终标记 (Final Marking)对用户线程做一个短暂的暂停用于处理并发阶段结束后仍遗留下来的少量的 STAB 记录。虽然并发标记阶段会处理 SATB 记录但由于处理时用户线程依然是运行中的因此依然会有少量的变动所以需要最终标记来处理④、筛选回收 (Live Data Counting and Evacuation)负责更新 Regin 统计数据按照各个 Regin 的回收价值和成本进行排序在根据用户期望的停顿时间进行来指定回收计划可以选择任意多个 Regin 构成回收集。然后将回收集中 Regin 的存活对象复制到空的 Regin 中再清理掉整个旧的 Regin 。此时因为涉及到存活对象的移动所以需要暂停用户线程并由多个收集线程并行执行。5.8 内存分配原则1. 对象优先在 Eden 分配大多数情况下对象在新生代的Eden区中进行分配当Eden区没有足够空间时虚拟机将进行一次 Minor GC。2. 大对象直接进入老年代大对象就是指需要大量连续内存空间的 Java 对象最典型的就是超长的字符串或者元素数量很多的数组它们将直接进入老年代。主要是因为如果在新生代分配因为其需要大量连续的内存空间可能会导致提前触发垃圾回收并且由于新生代的垃圾回收本身就很频繁此时复制大对象也需要额外的性能开销。3. 长期存活的对象将进入老年代虚拟机会给每个对象在其对象头中定义一个年龄计数器。对象通常在Eden区中诞生如果经历第一次 Minor GC 后仍然存活并且能够被 Survivor 容纳的话该对象就会被移动到 Survivor 中并将其年龄加 1。对象在 Survivor 中每经过一次 Minor GC年龄就加 1当年龄达到一定程度后由-XX:MaxTenuringThreshold设置默认值为 15就会进入老年代中。4. 动态年龄判断如果在 Survivor 空间中相同年龄的所有对象大小的总和大于 Survivor 空间的一半那么年龄大于或等于该年龄的对象就可以直接进入老年代而无需等待年龄到达-XX:MaxTenuringThreshold设置的值。5. 空间担保分配在发生 Minor GC 之前虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间如果条件成立那么这一次的 Minor GC 可以确认是安全的。如果不成立虚拟机会查看-XX:HandlePromotionFailure的值是否允许担保失败如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小如果大于将尝试着进行一次 Minor GC如果小于或者-XX:HandlePromotionFailure的值设置不允许冒险那么就要改为进行一次 Full GC 。六、类加载机制Java 虚拟机把描述类的数据从 Class 文件加载到内存并对数据进行校验、转换解析和初始化最终形成可以被虚拟机直接使用的 Java 类型这个过程被称为虚拟机的类加载机制。6.1 类加载时机一个类从被加载到虚拟机内存中开始到卸载出内存为止它的整个生命周期将会经历加载、验证、准备、卸载、解析、初始化、使用、卸载七个阶段其中验证、准备、解析三个部分统称为连接《Java 虚拟机规范》严格规定了有且只有六种情况必须立即对类进行初始化①、遇到new、getstatic、putstatic、invokestatic这四条字节码指令能够生成这四条指令码的典型 Java 代码场景有使用new关键字实例化对象时读取或设置一个类型的静态字段时被 final 修饰已在编译期把结果放入常量池的静态字段除外调用一个类的静态方法时。②、使用java.lang.reflect包的方法对 Class 进行反射调用时如果类型没有进行过初始化、则需要触发其初始化③、当初始化类时如发现其父类还没有进行过初始化、则需要触发其父类进行初始化④、当虚拟机启动时用户需要指定一个要执行的主类包含 main() 方法的那个类虚拟机会先初始化这个主类⑤、当使用 JDK 7 新加入的动态语言支持时如果一个java.lang.invoke.MethodHandle实例最后解析的结果为REF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecial四种类型的方法句柄并且这个方法句柄对应的类没有进行过初始化则需要先触发其初始化⑥、当一个接口中定义了 JDK 8 新加入的默认方法被 default 关键字修饰的接口方法时如果有这个接口的实现类发生了初始化那么该接口要在其之前被初始化。6.2 类加载过程1. 加载在加载阶段虚拟机需要完成以下三件事通过一个类的全限定名来获取定义此类的二进制字节流 将这个字节流所代表的静态存储结构转换为运行时数据结构在内存中生成一个代表这个类的java.lang.Class对象作为这个类的各种数据的访问入口。《Java 虚拟机规范》并没有限制从何处获取二进制流因此可以从 JAR 包、WAR 包获取也可以从 JSP 生成的 Class 文件等处获取。2. 验证这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求从而保证这些信息被当做代码运行后不会危害虚拟机自身的安全。验证阶段大致会完成下面四项验证文件格式验证验证字节流是否符合 Class 文件格式的规范元数据验证对字节码描述的信息进行语义分析以保证其描述的信息符合《Java 语言规范》的要求如除了java.lang.Object外所有的类都应该有父类字节码验证通过数据流分析和控制流分析确定程序语义是合法的符合逻辑的如允许把子类对象赋值给父类数据类型但不能把父类对象赋值给子类数据类型符号引用验证验证类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。如果无法验证通过则会抛出一个java.lang.IncompatibleClassChangeError的子类异常如java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。3. 准备准备阶段是正式为类中定义的变量即静态变量被 static 修饰的变量分配内存并设置类变量初始值的阶段。4. 解析解析是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程符号引用符号引用用一组符号来描述所引用的目标符号可以是任何形式的字面量只要使用时能无歧义地定位到目标即可。直接引用直接引用是指可以直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。整个解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行解析。5. 初始化初始化阶段就是执行类构造器的clinit()方法的过程该方法具有以下特点clinit()方法由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生编译器收集顺序由语句在源文件中出现的顺序决定。clinit()方法与类的构造方法即在虚拟机视角中的实例构造器init()方法不同它不需要显示的调用父类的构造器Java 虚拟机会保证在子类的clinit()方法执行前父类的clinit()方法已经执行完毕。由于父类的clinit()方法先执行也就意味着父类中定义的静态语句块要优先于子类变量的赋值操作。clinit()方法对于类或者接口不是必须的如果一个类中没有静态语句块也没有对变量进行赋值操作那么编译器可以不为这个类生成clinit()方法。接口中不能使用静态语句块但仍然有变量初始化的赋值操作因此接口与类一样都会生成clinit()方法。Java 虚拟机必须保证一个类的clinit()方法在多线程环境中被正确的加锁同步如果多个线程同时去初始化一个类那么只会有其中一个线程去执行这个类的clinit()方法其他线程都需要阻塞等待。6.3 类加载器能够通过一个类的全限定名来获取描述该类的二进制字节流的工具称为类加载器。每一个类加载器都拥有一个独立的类名空间因此对于任意一个类都必须由加载它的类加载器和这个类本身来共同确立其在 Java 虚拟机中的唯一性。这意味着要想比较两个类是否相等必须在同一类加载器加载的前提下如果两个类的类加载器不同则它们一定不相等。6.4 双亲委派模型从 Java 虚拟机角度而言类加载器可以分为以下两类启动类加载器启动类加载器Bootstrap ClassLoader由 C 语言实现以 HotSpot 为例它是虚拟机自身的一部分其他所有类的类加载器由 Java 语言实现独立存在于虚拟机外部并且全部继承自java.lang.ClassLoader。从开发人员角度而言类加载器可以分为以下三类启动类加载器 (Boostrap Class Loader)负责把存放在JAVA_HOME\lib目录中或被-Xbootclasspath参数所指定的路径中存放的能被 Java 虚拟机识别的类库加载到虚拟机的内存中扩展类加载器 (Extension Class Loader)负责加载JAVA_HOME\lib\ext目录中或被java.ext.dirs系统变量所指定的路径中的所有类库。应用程序类加载器 (Application Class Loader)负责加载用户类路径ClassPath上的所有的类库。JDK 9 之前的 Java 应用都是由这三种类加载器相互配合来完成加载上图所示的各种类加载器之间的层次关系被称为类加载器的 “双亲委派模型”“双亲委派模型” 要求除了顶层的启动类加载器外其余的类加载器都应该有自己的父类加载器需要注意的是这里的加载器之间的父子关系一般不是以继承关系来实现的而是使用组合关系来复用父类加载器的代码。双亲委派模型的工作过程如下如果一个类加载器收到了类加载的请求它首先不会自己去尝试加载这个类而是把这个请求委派给父类加载器去完成每一层的类加载器都是如此因此所有的加载请求最终都应该传送到最顶层的启动类加载器只有当父加载器反馈自己无法完成这个加载请求它的搜索范围中没有找到所需的类时子加载器才会尝试自己去完成加载。基于双亲委派模型可以保证程序中的类在各种类加载器环境中都是同一个类否则就有可能出现一个程序中存在两个不同的java.lang.Object的情况。6.5 模块化下的类加载器JDK 9 之后为了适应模块化的发展类加载器做了如下变化仍维持三层类加载器和双亲委派的架构但扩展类加载器被平台类加载器所取代当平台及应用程序类加载器收到类加载请求时要首先判断该类是否能够归属到某一个系统模块中如果可以找到这样的归属关系就要优先委派给负责那个模块的加载器完成加载启动类加载器、平台类加载器、应用程序类加载器全部继承自java.internal.loader.BuiltinClassLoaderBuiltinClassLoader 中实现了新的模块化架构下类如何从模块中加载的逻辑以及模块中资源可访问性的处理。七、程序编译7.1 编译器分类前端编译器把*.java文件转变成.class文件的过程如 JDK 的 JavacEclipse JDT 中的增量式编译器。即时编译器常称为 JIT 编译器Just In Time Complier在运行期把字节码转变成本地机器码的过程如 HotSpot 虚拟机中的 C1、C2 编译器Graal 编译器。提前编译器直接把程序编译成目标机器指令集相关的二进制代码的过程。如 JDK 的 jaotcGUN Compiler for the JavaGCJExcelsior JET 。7.2 解释器与编译器在 HotSpot 中Java 程序最初都是通过解释器Interpreter进行解释执行的其优点在于可以省去编译时间让程序快速启动。当程序启动后如果虚拟机发现某个方法或代码块的运行特别频繁就会使用编译器将其编译为本地机器码并使用各种手段进行优化从而提高执行效率这就是即时编译器。HotSpot 内置了两个或三个即时编译器客户端编译器 (Client Complier)简称 C1服务端编译器 (Servier Complier)简称 C2在有的资料和 JDK 源码中也称为 Opto 编译器Graal 编译器在 JDK 10 时才出现长期目标是替代 C2。在分层编译的工作模式出现前不管是采用客户端编译器还是服务端编译器完全取决于虚拟机是运行在客户端模式还是服务端模式下可以在启动时通过-client或-server参数进行指定也可以让虚拟机根据自身版本和宿主机性能来自主选择。7.3 分层编译要编译出优化程度越高的代码通常都需要越长的编译时间为了在程序启动速度与运行效率之间达到最佳平衡HotSpot 在编译子系统中加入了分层编译Tiered Compilation第 0 层程序纯解释执行并且解释器不开启性能监控功能第 1 层使用客户端编译器将字节码编译为本地代码来运行进行简单可靠的稳定优化不开启性能监控功能第 2 层仍然使用客户端编译执行仅开启方法及回边次数统计等有限的性能监控第 3 层仍然使用客户端编译执行开启全部性能监控第 4 层使用服务端编译器将字节码编译为本地代码其耗时更长并且会根据性能监控信息进行一些不可靠的激进优化。以上层次并不是固定不变的根据不同的运行参数和版本虚拟机可以调整分层的数量。各层次编译之间的交互转换关系如下图所示实施分层编译后解释器、客户端编译器和服务端编译器就会同时工作可以用客户端编译器获取更高的编译速度、用服务端编译器来获取更好的编译质量。7.4 热点探测即时编译器编译的目标是 “热点代码”它主要分为以下两类被多次调用的方法。被多次执行循环体。这里指的是一个方法只被少量调用过但方法体内部存在循环次数较多的循环体此时也认为是热点代码。但编译器编译的仍然是循环体所在的方法而不会单独编译循环体。判断某段代码是否是热点代码的行为称为 “热点探测” Hot Spot Code Detection主流的热点探测方法有以下两种基于采样的热点探测 (Sample Based Hot Spot Code Detection)采用这种方法的虚拟机会周期性地检查各个线程的调用栈顶如果发现某个或某些方法经常出现在栈顶那么就认为它是 “热点方法”。基于计数的热点探测 (Counter Based Hot Spot Code Detection)采用这种方法的虚拟机会为每个方法甚至是代码块建立计数器统计方法的执行次数如果执行次数超过一定的阈值就认为它是 “热点方法”。八、代码优化即时编译器除了将字节码编译为本地机器码外还会对代码进行一定程度的优化它包含多达几十种优化技术这里选取其中代表性的四种进行介绍8.1 方法内联最重要的优化手段它会将目标方法中的代码原封不动地 “复制” 到发起调用的方法之中避免发生真实的方法调用并采用名为类型继承关系分析Class Hierarchy AnalysisCHA的技术来解决虚方法Java 语言中默认的实例方法都是虚方法的内联问题。8.2 逃逸分析逃逸行为主要分为以下两类方法逃逸当一个对象在方法里面被定义后它可能被外部方法所引用例如作为调用参数传递到其他方法中此时称为方法逃逸线程逃逸当一个对象在方法里面被定义后它可能被外部线程所访问例如赋值给可以在其他线程中访问的实例变量此时称为线程逃逸其逃逸程度高于方法逃逸。publicstaticStringBuilderconcat(String...strings){StringBuildersbnewStringBuilder();for(Stringstring:strings){sb.append(string);}returnsb;// 发生了方法逃逸}publicstaticStringconcat(String...strings){StringBuildersbnewStringBuilder();for(Stringstring:strings){sb.append(string);}returnsb.toString();// 没有发生方法逃逸}如果能证明一个对象不会逃逸到方法或线程之外或者逃逸程度比较低只逃逸出方法而不会逃逸出线程则可以对这个对象实例采取不同程序的优化栈上分配 (Stack Allocations)如果一个对象不会逃逸到线程外那么将会在栈上分配内存来创建这个对象而不是 Java 堆上此时对象所占用的内存空间就会随着栈帧的出栈而销毁从而可以减轻垃圾回收的压力。标量替换 (Scalar Replacement)如果一个数据已经无法再分解成为更小的数据类型那么这些数据就称为标量如 int、long 等数值类型及 reference 类型等反之如果一个数据可以继续分解那它就被称为聚合量如对象。如果一个对象不会逃逸外方法外那么就可以将其改为直接创建若干个被这个方法使用的成员变量来替代从而减少内存占用。同步消除 (Synchronization Elimination)如果一个变量不会逃逸出线程那么对这个变量实施的同步措施就可以消除掉。8.3 公共子表达式消除如果一个表达式 E 之前已经被计算过了并且从先前的计算到现在 E 中所有变量的值都没有发生过变化那么 E 这次的出现就称为公共子表达式。对于这种表达式无需再重新进行计算只需要直接使用前面的计算结果即可。8.4 数组边界检查消除对于虚拟机执行子系统来说每次数组元素的读写都带有一次隐含的上下文检查以避免访问越界。如果数组的访问发生在循环之中并且使用循环变量来访问数据即循环变量的取值永远在[0list.length)之间那么此时就可以消除整个循环的数据边界检查从而避免多次无用的判断。小结这篇文章我们系统地总结了 JVM 最重要的知识点比如说 JVM 的内存结构、垃圾回收算法、垃圾回收器、类加载机制、类加载器、程序编译、代码优化等等希望能对大家在学习 JVM 的时候有所帮助。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

安阳中飞网站建设泉州正规制作网站公司

深入了解SELinux:增强Linux系统安全性 1. 进程安全上下文与命令使用 在Linux系统中,进程的安全上下文在程序运行前就已设定,且取决于启动它的主体。如果某个守护进程有专门为其编写的SELinux策略,那么该进程会遵循此策略;若没有,则会被分配默认类型 unconfined_t 。 …

张小明 2026/1/4 10:41:52 网站建设

深圳坪山站wordpress ios源码

第一章:C扩展如何让Python飞起来,揭秘高性能计算背后的秘密武器Python以简洁易读著称,但在处理高并发、密集型计算时性能受限。其根本原因在于CPython解释器的GIL(全局解释器锁)和动态类型机制带来的运行时开销。为突破…

张小明 2026/1/4 10:41:50 网站建设

深圳莲花大厦住房和建设局网站深圳方维网站建设

公共交通系统仿真 1. 公共交通系统仿真的重要性 公共交通系统仿真是微观交通流仿真软件中一个非常重要的模块。它能够帮助交通规划师和工程师在虚拟环境中测试和评估不同的公共交通方案,从而优化公共交通系统的运行效率和服务质量。通过仿真,可以模拟公…

张小明 2026/1/4 10:41:48 网站建设

东莞英文网站设计企业采购平台哪个好

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 开发一个智能客服WebUI系统,包含以下功能:1) 用户聊天界面 2) 常见问题自动回复 3) 人工客服转接按钮 4) 对话历史记录。使用Vue.js框架,集成一个…

张小明 2026/1/10 23:53:13 网站建设

做一网站困难吗网站模板 安装

还在纠结要不要转行?捧着大模型理论资料却迟迟不动手?作为一名30北漂程序员,我用2个月时间完成零基础转行,成功拿下月薪2W的大模型岗位offer。今天把我的真实经历和避坑指南全分享出来,尤其适合想跨界的程序员和刚入门…

张小明 2026/1/8 11:16:27 网站建设