如何做图片网站,搭建网站的步骤,静态网站后台,找外包公司做网站这次社招选的这本作为 JVM 资料查阅#xff0c;记录一些重点
1. 虚拟机历史
Sun Classic VM #xff1a;已退休
HotSpot VM#xff1a;主流虚拟机#xff0c;热点代码探测技术
Mobile / Embedded VM #xff1a;移动端、嵌入式使用的虚拟机
2.2 运行时数据区域 程序计…这次社招选的这本作为 JVM 资料查阅记录一些重点
1. 虚拟机历史
Sun Classic VM 已退休
HotSpot VM主流虚拟机热点代码探测技术
Mobile / Embedded VM 移动端、嵌入式使用的虚拟机
2.2 运行时数据区域 程序计数器线程级当前线程所执行的字节码的行号指示器。
虚拟机栈线程级存放局部变量、操作数栈、动态链接、方法出口等信息。其中局部变量包含编译时可知的基本数据类型和对象引用。
本地方法栈线程级与虚拟机栈类似为虚拟机使用到的本地方法服务。
堆对象分配。
方法区存储已经被虚拟机加载的类型信息、常量、静态变量等。
2.2 补充 - 直接内存
直接内存不是虚拟机运行时数据区的一部分。使用 Native 函数直接分配堆外内存然后通过一个存储在堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。
2.3 对象的创建
1. 读到 new 指令。
2. 检查这个指令的参数能否在常量池定位到一个类的符号引用。检查这个类是否已经加载、解析、初始化若没有则执行。
3. 分配内存对象所需的内存大小在类加载完成后就可以完全确定。
4. 初始化为零值。
5. 记录对象头。
6. 初始化。 对对象的访问有句柄式和直接指针两种类型
2.3 补充 - 分配内存时保持线程安全
方案一对分配内存空间的动作进行同步处理
方案二每个线程在堆中预先分配一小块内存优先使用本地缓冲区耗尽后才需要进行同步锁定。
2.3 补充 - 对象的内存布局
对象头存储对象自身的运行时数据Hashcode锁等类型指针对象指向其类型元数据的指针当对象是一个 java 数组时还需要记录数组长度。
实例数据包含父类 子类的数据。
对齐补充补充为 8 字节的整数倍。 3.2 判断对象是否可以回收
1. 引用计数法利用引用计数器来记录引用数量。无法解决循环引用问题。
2. 可达性分析通过 GC Roots 向下搜索如果某个对象不可达则说明不再使用。可以作为 GC Roots 的对象包含虚拟机栈中引用的对象、方法区中静态属性引用的对象、方法区常量引用的对象、虚拟机内部的引用、被同步锁持有的对象、反映虚拟机内部情况的变量。
强引用通过引用赋值 Object obj new Object()
软引用用于描述一些还有用但非必须得对象。 SoftReferrence系统要发生移除异常前才进行回收。
弱引用对象只活到下次 GC 之前。WeakReferrence。
虚引用不影响回收作用仅是在回收时可以收到一个系统通知。 PhantomReference 。 对象在可达性分析后发现不可达进行第一次标记 - 放入 F - Queue 中 - Finalizer 线程执行 finalize() 方法 - 对 F - Queue 中的对象进行二次标记。 在枚举根节点时必然会停顿用户进程。
3.2 补充 方法区中的回收
主要回收废弃的常量和不再使用的类型。不再使用的类型该类的所有实例都已经回收 类加载器已经回收 该类的 Class 对象没有任何引用 3.3 分代收集基础上的垃圾回收算法
1. 标记 - 清除算法
2. 标记 - 复制算法内存分为大小相等的两块每次只使用其中的一块。Appel 式回收将内存划分为一块 Eden 区两块 Survivor 区域每次使用 Eden 一块 Survivor当出现极限情况会占用老年代内存。
3. 标记 - 整理算法存活的对象需要移动到整理 3.4 安全点 安全区域
安全点用户程序执行可以停下来的时间点
安全区域安全点无法保证挂起的线程可以执行到可以视作延长了的安全点。
如果线程在执行关键操作如执行系统调用时收到挂起请求JVM可能会延迟挂起直到线程完成当前操作并进入下一个安全点。
虽然用户可以通过Thread.suspend()方法请求挂起一个线程但JVM可能会根据当前的执行环境和线程状态延迟挂起操作直到线程到达一个安全点。这种做法有助于确保程序的稳定性和数据的一致性。然而需要注意的是Thread.suspend()方法已经被标记为过时deprecated并且不推荐在现代Java应用程序中使用因为它可能会导致死锁和其他问题。现代Java应用程序更倾向于使用Thread.interrupt()方法来请求线程中断并通过轮询中断状态来实现线程的协作挂起。 3.5 卡表
为了解决跨代引用问题新生代会维护一个「记忆集」避免把整个老年代都加入 GC Roots 的扫描范围。通常使用卡表来作为解决方案。 只要对应的内存中存在一个跨代指针就标记扫描时将其加入范围内。通过「写屏障」技术在每次赋值之后维护。
3.6 垃圾回收器
Serial 单线程工作。Stop the world。
ParNewSerial 的多线程版本。
Parallel基于标记复制算法尽可能达到一个可控制的吞吐量。适用于后台运算不需要太多交互的任务。
Serial OldSerial 的老年代版本。
Parallel Old Parallel 的老年代版本。
CMS最短响应时间。标记清除算法。
G1虽然保留了新生代和老年代的概念但不再固定区域转为划分为 Region每个 Region 可以独立作为某个代。
Shenandoah提供并发标记、并发回收、并发引用更新的处理旨在提供最小停顿时间。
ZGC基于 Region 内存布局的不设分代的使用了读屏障、染色指针和内存多重映射技术实现的可以并发的标记整理算法的垃圾处理器。 3.6 补充 G1 的卡表
CMS垃圾收集器的记忆集设计 记忆集的作用 记忆集用于记录从非收集区域指向收集区域的指针集合。在CMS中主要是记录老年代中哪些对象引用了新生代的对象71。 卡表的实现 卡表是记忆集的具体实现方式之一。在CMS中卡表是一个字节数组每个字节对应一个卡页通常是512字节。如果卡页中的某个对象引用了新生代的对象对应的卡表字节会被标记为171。 卡表的更新 在CMS的并发标记阶段如果老年代对象引用了新生代对象卡表会被更新标记相应的卡页为“脏卡”。这样在Minor GC时只需要扫描这些脏卡对应的老年代对象71。 并发标记和重新标记 CMS的并发标记阶段会并发地进行GC Roots Tracing而重新标记阶段则会修正并发标记阶段由于用户程序变动导致的问题71。
G1垃圾收集器的记忆集设计 记忆集的复杂性 G1的堆内存被划分为多个大小相等的区域Region每个区域可以是Eden、Survivor或老年代区域。G1的记忆集需要记录跨Region的引用关系72。 卡表的局限性 在G1中由于堆内存的划分方式传统的卡表结构不再适用。G1采用了“空间换时间”的策略通过增加记忆集的结构复杂度来减少GC的时间78。 记忆集的实现 G1的记忆集在概念上采用了point-in的思想即记录了哪个区域指向我。这种记忆集在Card Table的基础上增加了HashTable的数据结构Key是某个老年代Region的起始位置Value是这个老年代Region的所有存在跨代指针的卡页的起始位置的集合78。 跨Region引用的处理 G1的每个Region都维护有自己的记忆集记录了其他Region中的对象到该Region的引用。这样在进行垃圾回收时只需要扫描这些记录的引用关系而不需要扫描整个堆77。 记忆集的空间开销 G1的每个Region都维护有自己的记忆集这导致G1比其他垃圾收集器有着更高的内存占用负担。根据经验G1至少要耗费大约相当于Java堆容量10%至20%的额外内存来维持收集器工作
7.2 类加载的时机 加载
- 通过类的类名来获取定义此类的二进制字节流
- 字节流转化为方法区的运行时数据结构
- 生成 .Class 对象
准备
- 为类中定义的变量分配内存并设置初始值。
解析
- 常量池里的符号引用替换为直接引用的过程。包含接口、字段、方法、接口方法等 8.2 运行时栈 8.3 重载和重写是如何实现的 - 分派
静态分派
Human man new Man()其中 Human 为变量的静态类型或外观类型编译时可知。Man 为实际类型或运行时类型编译时不可知eg通过计算才确定 new 啥的写法。
Java 中的静态分配由于同时参考静态类型 参数所以属于多分派类型。
重载是通过静态类型进行判断的。
动态分派
在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
重写即为动态分派的体现根源在于 invokevirtual 指令的运行逻辑会优先使用实际类型。事实上Java 中只有虚方法存在字段永远不可能是虚的子类会屏蔽父类的同名字段。
Java 中的动态分派属于单分派类型只受实际类型影响。
8.3 补充 - 虚方法表
动态分派的实现方式之一类型在方法区会建立一个虚方法表用虚方法表代替元数据查找。虚方法表中存放着各个方法的实际入口地址。若没有重写子类的虚方法表和父类相同方法的入口一致都指向父类的实现入口。
此外还会他用过类型集成关系分析、守护内联、内联缓存等来争取更大的优化。
8.4 动态类型语言
动态类型语言的类型检查主体过程在运行期而不是编译期。Java 是静态语言。
8.5 解释执行 编译执行 编译执行 (Compile and Execute): 编译阶段源代码如C、C、Java等语言编写的程序首先需要通过编译器转换成机器代码或字节码。编译器检查源代码的语法错误进行类型检查优化代码最终生成可执行文件或字节码文件。执行阶段编译后的机器代码由计算机的操作系统加载并执行或者字节码由虚拟机如Java虚拟机加载并解释执行。 解释执行 (Interpret and Execute): 解释执行通常指的是源代码直接由解释器逐行解释并立即执行无需编译成机器代码。这种执行方式常见于脚本语言如Python、JavaScript、Ruby等。解释器读取源代码转换为中间表示如果需要然后立即执行这些操作而不需要等待整个程序编译完成。 即时编译 (Just-In-Time Compilation, JIT): 某些语言如Java使用即时编译技术将字节码在运行时编译成机器代码。这种方式结合了编译执行和解释执行的优点允许程序在开始时快速启动像解释执行并在运行过程中优化性能像编译执行。
10.3 泛型
Java 中的泛型为类型擦除式泛型只在源码中存在在编译后的字节码文件中全部泛型都被替换为原来的裸类型。
10.3 补充 自动装箱 拆箱
Java 的自动装箱Autoboxing和拆箱Unboxing是 Java 5 引入的两个特性它们允许基本数据类型如 int、double 等和对应的包装类如 Integer、Double 等之间的自动转换。
### 自动装箱Autoboxing 自动装箱是指自动将基本数据类型转换为对应的包装类类型。这个过程是编译器在代码编译时自动完成的。例如
java Integer refInt 5; // 自动装箱将 int 类型 5 转换为 Integer 类型
在这个例子中整数值 5 被自动转换为 Integer 对象。
### 自动拆箱Unboxing 自动拆箱是指自动将包装类类型转换为对应的基本数据类型。这同样是由编译器在编译时自动完成的。例如
java int num refInt; // 自动拆箱将 Integer 类型转换为 int 类型
在这个例子中refInt 是 Integer 类型的对象它被自动转换为 int 类型的变量 num。
### 转换规则 - int 与 Integer - double 与 Double - float 与 Float - long 与 Long - short 与 Short - byte 与 Byte - char 与 Character - boolean 与 Boolean
### 注意事项 - 自动装箱和拆箱在编译时由编译器处理因此在运行时不会有明显的性能损失。 - 包装类 Long、Integer 和 Short 提供了缓存机制对于一定范围内的值通常在 -128 到 127 之间会使用相同的实例来避免创建过多的对象。这个范围可以通过 java.lang.Integer.IntegerCache 高度自定义。 - 过度使用自动装箱可能导致性能问题尤其是在涉及到大量数据的情况下因为它会增加对象的创建和垃圾回收的负担。 - 在进行算术运算时如果涉及到基本数据类型和包装类型Java 会自动拆箱基本数据类型然后再进行运算。
自动装箱和拆箱使得在需要使用对象的情况下可以更加方便地使用基本数据类型同时保持代码的简洁性和可读性。然而开发者应该注意它们可能带来的性能影响并在适当的时候手动进行装箱或拆箱操作。
11.3 解释器和编译器 程序需要快速启动和执行的时候解释器可以首先发挥作用省去编译时间立刻运行。程序启动后编译器逐渐发生作用把越来越多的代码译为本地代码获得更高的执行效率。
11.4 方法内联
原理上是将目标方法的代码复制到发起调用的方法之中减少方法调用。但实际需要进行很多优化准备因为大部分调用都是虚方法。例如采用类型继承关系分析、内联缓存等。
11.5 逃逸分析
分析对象动态作用域当一个对象在方法里面被定义后可能被外部方法引用称为方法逃逸。被外部线程访问称为线程逃逸。
若一个对象逃逸程度较低可以采取不同程度的优化
1. 栈上分配如果确定对象不会逃逸到线程外直接分配在栈上。
2. 标量替换如果对象不会被方法外访问可以将其拆分到最小基本类型。分配到栈上。
3. 同步消除如果对象不会被线程外访问可以消除其同步措施。
11.6 公共子表达式消除
如果一个表达式之前已经被计算过了并且从之前的计算到现在E中所有变量都没有变化那么无需重复计算。 12.3 Java 内存模型 12.4 Volatile
1. 保证此变量对所有线程的可见性
适用于运算结果并不依赖变量的当前值或者能确保只有单一线程修改此值变量不需要与其他状态变量共同参与不变约束。
2. 禁止指令重排序
12.5 原子性、可见性、有序性
1. 原子性基本数据类型的访问读写都是原子性的当需要更大范围的保证时提供了 synchronized 关键字。
2. 可见性通过变量修改后把新值同步回主内存在读取变量前从主内存刷新变量值来实现。此外 synchronized 和 final 关键字也可以保证可见性。
3. 有序性在本线程内观察所有操作都是有序的在另外一个线程中观察则是无序的。 volatile 和 synchronized 都可以保证。
12.6 先行发生原则
先行发生内存模型中定义的两项操作之间的偏序关系即发生操作B之前操作A的影响能被B观察到。
1. 程序次序规则一个线程内按控制流顺序执行。
2. 管程锁定原则 unlock 晚于 lock
3. volatile 对 volatile 的写先行发生于读
4. 线程启动线程的 start() 动作先行发生于此线程的每一个动作
5. 线程终止线程的所有操作都先行发生于对此线程的终止检测
6. 线程中断对 interrupt的调用先行发生于被中断线程的代码检测到中断事件的发生
7. 对象终止初始化先行于 finalize()
8. 传递性。
12.7 线程状态 13.1 线程安全
线程安全的不同级别
1. 不可变例如 final 修饰的变量。
2. 绝对线程安全很难达到
Vector 是 Java 中的一个同步的 List 实现它的方法默认都是同步的这意味着在多线程环境下多个线程可以安全地访问 Vector 对象而不需要额外的同步控制。
然而即使 Vector 的方法是同步的这并不意味着在多线程环境下对 Vector 进行的所有操作都是安全的。以下是一些可能导致问题的情况 迭代器失效 如果在一个线程中正在遍历 Vector而另一个线程删除了元素那么遍历的迭代器可能会失效。这是因为删除操作可能会改变底层数组的结构导致迭代器的状态与实际数据不一致。 并发修改异常 尽管 Vector 的方法是同步的但如果在一个线程中删除元素后另一个线程立即尝试访问该元素可能会抛出 ConcurrentModificationException。这是因为 Vector 无法保证在删除操作和访问操作之间的原子性。 索引变化 当一个线程删除了一个元素后Vector 的大小会减小但其他线程可能还不知道这个变化。如果这些线程仍然使用旧的索引来访问元素可能会访问到错误的位置甚至抛出 IndexOutOfBoundsException。 可见性问题 在多线程环境中一个线程对 Vector 的修改可能对其他线程不可见直到修改后的值被写回到主内存。如果其他线程没有看到最新的值可能会使用错误的数据。 方法级别的同步 Vector 的同步是方法级别的这意味着每次调用方法时都会进行同步。但是如果一个线程在执行一个方法的过程中被中断而另一个线程在此时调用了另一个方法可能会发生不一致的状态。
3. 相对线程安全对象的单次操作是线程安全的但对于一些特定顺序的连续调用需要额外的手段来保证正确性。Java 中大部分声称线程安全的对象都属于此类。
4. 线程兼容
5. 线程对立无论是否采取了同步措施都无法在多环境使用。
13.2 线程安全的实现方法
1. 互斥同步保证共享数据值被一条线程使用。例如 synchronized 和 lock
2. 非阻塞同步CAS 方案
3. 无同步方案