普洱市建设局网站,在什么网站做推广最好,wordpress首页调用缩略图,用地方别名做网站名目录
一、JVM简介
二、JVM中的内存区域划分
三、JVM加载
1.类加载
1.1 加载
1.2 验证
1.3 准备
1.4 解析
1.5 初始
1.6 总结
2.双亲委派模型
四、JVM 垃圾回收#xff08;GC#xff09;
1.确认垃圾
1.1 引用计数
1.2 可达性分析#xff08;Java 采用的方案GC
1.确认垃圾
1.1 引用计数
1.2 可达性分析Java 采用的方案
2.释放“垃圾”对象
2.1 标记清除
2.2 复制算法
2.3 标记整理
2.4 分代回收
3.垃圾收集器
3.1 CMS收集器老年代收集器并发GC
3.2 G1收集器(唯一一款全区域的垃圾回收器) 在JVM中我们主要去讨论以下的问题
1. JVM中的内存区域划分
2. JVM的类加载机制
3. JVM中的垃圾回收策略 一、JVM简介
JVM 是 Java Virtual Machine 的简称意为 Java 虚拟机
虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统
JVM的执行流程 程序在执行之前先要把 java 代码转换成字节码 class 文件 JVM 首先需要把字节码通过一定的方式 类加载器 ClassLoader 把文件加载到内存中 运行时数据区 Runtime Data Area 而字节码 文件是 JVM 的一套指令集规范并不能直接交个底层操作系统去执行因此需要特定的命令解析器 执 行引擎 Execution Engine 将字节码翻译成底层系统指令再交由 CPU 去执行而这个过程中需要调 用其他语言的接口 本地库接口 Native Interface 来实现整个程序的功能这就是这 4 个主要组成部分的职责与功能。 二、JVM中的内存区域划分
JVM其实是一个 Java 进程Java进程会从操作系统申请一大块区域给java代码使用。
这一大块区域进一步划分出不同的用途
堆new 出来的对象成员变量栈维护方法之间的调用关系局部变量方法区旧/ 元数据区新放的是类加载之后的类对象静态变量 经典考点给一段代码问某个变量处于内存的哪个区域 看变量的形态局部变量、成员变量、静态变量 具体内存区域划分 总结 1.堆的作用程序中创建的所有对象都在保存在堆中 2.Java 虚拟机栈的作用Java 虚拟机栈的生命周期和线程相同Java 虚拟机栈描述的是 Java 方法执行的内存模型每个方法在执行的同时都会创建一个栈帧Stack Frame用于存储局部变量表、操作数栈、动态链接、方法出口等信息。咱们常说的堆内存、栈内存中栈内存指的就是虚拟机栈 3.虚拟机栈给 java 代码使用 4.本地方法栈给 jvm 内部的本地方法使用的JVM 内部通过 C代码实现的方法 5.程序计数器记录当前程序指定到哪个指令相当于一个简单的 long 类型的变量存了一个内存地址内存地址就是下一个要执行的 字节码 所在的地址 6.方法区的作用用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的。 7.堆和元数据区在JVM进程中只有一份栈本地方法栈和程序计数器则是存在多份每个线程都有一份 三、JVM加载
1.类加载
简单来说就是把 .class 文件加载到内存得到类对象的过程
对于一个类来说可以总结成五个词要记住 1.1 加载
即找到 .class 文件并且读文件内容
1.2 验证
验证是连接阶段的第一步这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
1.3 准备
给类对象分配内存空间类加载最终就是为了得到类对象即未初始化的空间内存空间中的数据全是0类对象中的静态成员也是全0的
1.4 解析
针对 字符串常量 进行初始化也就是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程 字符串常量在 .class 文件中就存在了但是他们只是知道彼此之间的相对位置偏移量不知道自己在内存中的实际地址这个时候的字符串常量就是符号引用 真正加载到内存中就会把字符串常量填充到内存中的特定地址上字符串常量之间的相对位置还是一样的但是这些字符串有了自己真正的内存地址此时的字符串就是直接引用Java中的普通引用 例如一个班级组织学生去看电影学生需要在操场集合站队的时候谁在前谁在后位置是固定的这个时候张三和李四在一排站着但是在路上走的时候可能走着走着队伍变形了这个时候张三想和李四一起走就需要进行调整保持一个走位和李四走在一起最后仍然可以保证进场之后张三和李四坐在一起 1.5 初始
针对类对象进行初始化初始化静态成员执行静态代码块类要是有父类还需加载父类...
1.6 总结
类加载什么时候触发不是 jvm 一启动就会把所有的 .class 都加载了整体是一个“懒加载”的策略懒汉模式即非必要不加载 什么叫做“必要” 1.创建了类的实例 2.使用了这个类的静态方法/静态属性 3.使用子类会触发父类的加载 2.双亲委派模型
类加载中最关键的一个考点——双亲委派模型在第一个步骤中找 .class 文件 这个过程 如果一个类加载器收到了类加载的请求它首先不会自己去尝试加载这个类而是把这个请求委派给父类加载器去完成每一个层次的类加载器都是如此因此所有的加载请求最 终都应该传送到最顶层的启动类加载器中只有当父加载器反馈自己无 法完成这个加载请求它的搜索范围中没有找到所需的类时子加载器才会尝试自己去完成加载。 在JVM中内置了三个 类加载器
BootStrap ClassLoader负责加载 Java标准库中的类Extension ClassLoader负责加载一些非标准的 Sun/ Oracle 扩展库的类Application ClassLoader负责加载项目中自己写的类以及第三方库中的类
具体加载一个类的时候过程如下需要先给定一个类的全限定类名“java.lang.String”字符串 以上历程在日常工作中也经常存在把 BootStrap 想象成公司老板Extension 想象成主管Application 想象成基层员工
双亲委派模型优点
1.避免重复加载类比如 A 类和 B 类都有一个父类 C 类那么当 A 启动时就会将 C 类加载起来那么在 B 类进行加载时就不需要在重复加载 C 类了。
2.安全性使用双亲委派模型也可以保证了 Java 的核心 API 不被篡改如果没有使用双亲委派模型而是每个类加载器加载自己的话就会出现一些问题比如我们编写一个称为 java.lang.Object类的话那么程序运行的时候系统就会出现多个不同的 Object 类而有些 Object 类又是用户自己提供的因此安全性就不能得到保证了。
四、JVM 垃圾回收GC
帮助程序猿自动释放内存的 C语言中malloc 的内存必须手动 free否则就容易出现内存泄露光申请内存不释放内存逐渐用完了导致程序崩溃内存泄漏是C程序猿职业生涯中的头号杀手Java等后续的编程语言引入了 GC 来解决上述问题能有效的减少内存泄漏的出现概率。 内存的释放是一件比较纠结的事情申请的时机是明确的使用了必须要申请释放的时机是模糊的彻底不使用才能释放。C/C做法是完全让程序猿来决定比较不靠谱的特别依赖程序猿的水平而 Java 通过 JVM 自动判定基于一系列策略就可以让这个准确性比较高但是也会付出一些代价 JVM 中的内存有好几个区域是释放哪个部分的空间堆new 出来的对象 程序计数器就是一个单纯存地址的整数不需要是随着线程一起销毁栈也是随着线程一起销毁方法调用完毕方法的局部变量自然随着栈操作就销毁了元数据区/方法区存的类对象很少会“卸载”。 堆!!!就成了GC的主要目标 GC也就是以对象为单位进行释放的说是释放内存其实是释放对象 GC中主要分成两个阶段
1.找——确认垃圾
2.释放——把垃圾对象的内存给释放掉
这个时候就涉及到了垃圾回收算法
1.确认垃圾
一个对象如果后续再也没用了就可以认为是垃圾Java中使用一个对象只能通过引用如果一个对象没有引用指向它此时这个对象一定是无法被使用的托托的是垃圾如果一个对象已经不想用了但是这个引用可能还指向着呢这种情况下JVM是没法考虑到的因此 Java 中只是单纯通过引用没有指向这个操作来判定垃圾的
java 怎样知道一个对象是否有引用指向呢
1.1 引用计数
给对象里安排一个额外的空间保存一个整数表示对象有几个引用指向Java 实际上没使用这个方案Python、PHP采取的 两个缺陷
1️⃣浪费内存空间
2️⃣存在循环引用的情况会导致引用计数的判定逻辑出错 1.2 可达性分析Java 采用的方案
把对象之间的引用关系理解成一个树形结构从一些特殊的七点触发进行遍历只要能遍历访问到的对象就是“可达”再把“不可达的”当做垃圾即可 可达性分析关键要点进行上述遍历需要有“起点”GC Roots
1️⃣栈上的局部变量每个栈的每个局部变量都是起点
2️⃣常量池中引用的对象
3️⃣方法区中静态成员引用的对象 可达性分析从所有的 GC Roots 的起点出发看看该对象里又通过引用能访问哪些对象顺藤摸瓜把所有可以访问的对象都给遍历一遍遍历的同时把对象标记成“可达” 剩下的自然就是“不可达” 可达性分析克服了引用计数器的两个缺点但是也有自己的问题
1️⃣消耗更多的时间某个对象成了垃圾也不一定第一时间发现因为扫描的过程需要消耗时间的
2️⃣在进行可达性分析的时候要顺藤摸瓜一旦这个过程中当前代码中对象的引用关系发生变化了就麻烦了
因此为了更准确的完成这个“摸瓜”的过程需要让其他的业务线程暂停工作(STW 问题)
2.释放“垃圾”对象
三种典型的策略1️⃣标记清除2️⃣复制算法3️⃣标记整理
2.1 标记清除 2.2 复制算法
把整个内存空间分成两段一次只用一半 复制算法解决了内存碎片问题但是也有缺点
1️⃣内存利用率比较低
2️⃣如果当前的对象大部分都要保留的垃圾很少此时复制成本就比较高
2.3 标记整理
类似于顺序表删除中间元素有一个搬运过程 1️⃣解决内存碎片问题
2️⃣搬运开销比较大不适合频繁进行 以上三种策略不能直接解决问题因此实际上JVM 的实现思路是结合上述几种思想方法——分代回收 2.4 分代回收
分代回收思想给对象设定了“年龄”这样的概念即描述了这个对象存在多久了如果一个对象刚诞生认为是0岁每次经过一轮扫描可达性分析如果没被标记成垃圾这个时候对象就涨一岁。通过年龄来区分对象的存活时间经验规律如果一个对象存活时间很长了他将继续存在更长的时间
分代回收针对不同的情况使用不同的策略取长补短即针对不同年龄的对象采取不同的回收策略 把Java堆分为新生代和老年代 1️⃣新创建的对象放在伊甸区当垃圾回收扫描到伊甸区之后绝大部分对象都会在第一轮 GC 中就会被干掉即大部分对象活不过一岁
2️⃣如果伊甸区的对象熬过第一轮 GC就会通过复制算法拷贝到生存区生存区分成两半大小均等一次只使用其中一半垃圾回收扫描伊甸区的对象也是发现垃圾就淘汰对于不是垃圾通过 复制算法 复制到生存区的另一半
3️⃣当这个对象在生存区熬过若干轮 GC 之后年龄增长到一定程度了就会通过 复制算法 拷贝到老年代
4️⃣进入老年代的对象年龄都挺大再消亡的概率比前面新生代中的对象小不少针对老年代的 GC 的扫描频次就会降低很多如果老年代中发现某个对象是垃圾了使用标记整理的方式清除
5️⃣特殊情况如果对象非常大直接进入老年代大对象进行复制算法成本比较高而且大对象也不会很多 3.垃圾收集器 垃圾收集器是垃圾回收算法引用计数法、标记清楚法、标记整理法、复制算法的具体实现不同垃圾收集器、不同版本的JVM所提供的垃圾收集器可能会有很在差别。 垃圾回收器是具体的实现方式具体实现的时候往往基于上述思想方法做出一些改进和优化包含更多复杂的细节只作为了解即可java版本的变更垃圾回收器也在不断变化 3.1 CMS收集器老年代收集器并发GC
CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上这类应用尤其重视服务的响应速度希望系统停顿时间最短以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
特点
针对老年代采用标记-清楚法清除垃圾基于标记-清除算法(不进行压缩操作产生内存碎片)以获取最短回收停顿时间为目标并发收集、低停顿CMS收集器有3个明显的缺点1.对CPU资源非常敏感、2.无法处理浮动垃圾可能出现Concurrent Mode Failure失败、3.产生大量内存碎片垃圾收集线程与用户线程基本上可以同时工作
3.2 G1收集器(唯一一款全区域的垃圾回收器)
G1Garbage First垃圾回收器是用在heap memory很大的情况下把heap划分为很多很多的region块然后并行的对其进行垃圾回收G1垃圾回收器在清除实例所占用的内存空间后还会做内存压缩。G1垃圾回收器回收region的时候基本不会STW而是基于 most garbage优先回收(整体来看是基于标记-整理算法从局部(两个region之间)基于复制算法) 的策略来对region进行垃圾回收的
能充分利用多CPU、多核环境下的硬件优势可以并行来缩短(Stop The World)停顿时间也可以并发让垃圾收集与用户程序同时进行分代收集收集范围包括新生代和老年代能独立管理整个GC堆新生代和老年代而不需要与其他收集器搭配能够采用不同方式处理不同时期的对象应用场景可以面向服务端应用针对具有大内存、多处理器的机器采用标记-整理 复制算法来回收垃圾