JVM学习记录
-
数据是商品,硬盘是仓库,内存是货架,买东西是只能在货架上买的
-
货架容纳不下当前需要摆放的商品,即内存溢出
-
JVM 虚拟出一个计算机来仿真模仿各种计算机功能
-
JVM 有自己的硬件架构,如处理器、堆栈、寄存器等,还有对应分指令系统
-
JVM是淘宝店铺,不是真是存在的货架但能上架下架商品,数量也是有限
-
平台无关性可以理解成异地购物
-
内存模型就是具体的各变量访问规则
-
所有变量保存主内存,每条线程有自己的工作内存保存用到变量的内存副本
-
不能直接读写内存的变量
-
不同线程无法访问对方的工作内存变量,值传递要通过主内存进行
-
执行引擎相当于CPU
-
8种操作完成主内存与工作内存的交互协议
-
lock锁定变量,将其变成一条线程独占
-
unlock 解锁
-
read 将主内存变量传输到线程的工作内存 -> load 将取得变量放入变量副本
主内存->read->load->工作内存
-
write 将store得到的变量副本写入到主内存
工作内存->store->write->主内存
-
use 将工作内存的变量传递给执行引擎
工作内存->use->执行引擎
-
assign 将执行引擎收到的值赋给工作内存
执行引擎->assign->工作内存
-
store 将工作内存的变量传送到主内存供write使用
-
JVM内存划分:线程私有,线程共享
-
共享:方法区,堆
-
私有:虚拟机栈,程序计数器,本地方法栈
-
局部变量表会随着函数栈帧的销毁而销毁,包括基本数据类型、对象引用、字节码指令地址
-
操作栈保存计算过程的中间结果
-
每个栈帧包含一个指向运行时常量池中该帧所属方法引用,持有该引用是为了动态连接
-
正常完成出口,异常完成出口,无论哪种形式推出,都需要返回到方法被调用位置
程序计数器
-
程序计数器是一块较小的内存空间,可以看作是当前线程执行的字节码的行号指示器。
-
为了线程切换后能恢复到正确的执行位置,每条线程都有一个私有的计数器。
-
程序计数器是唯一一个在 Java 虚拟机规范中没有规定任何 OOM 情况的区域。
虚拟机栈
-
虚拟机栈就是Java方法栈,每个方法执行时创建一个栈帧(Stack Frame),栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,生命周期与线程相同。
-
虚拟机栈的两种异常:StackOverflowError(栈请求深度大于规定的最大深度),OutOfMemoryError(扩展申请不到足够的内存)
本地方法栈(Nativie Method Stack)
- 为Native方法服务
- 虚拟机栈一样的异常
Java堆
- 存放创建的对象实例 即是所有的变量
- 最大的内存区域
- 虚拟机创建时创建
- GC管理的主要区域
方法区
- 线程共享
- 已被虚拟机加载的数据
- 存储类型:类信息 常量 静态变量 即时编译器编译后的代码
- 此区大小决定可以保存多少个类,定义过多溢出,会抛出OutOfMemoryError
- 分运行时常量池和直接内存
- Class 文件中除了有类的版本、字段、方法和接口等描述信息,还有一项信息就是常量池(Constant Pool Table)。 常量池->运行时常量池
- 直接内存
- 直接内存的分配不会受到 Java 堆大小的限制,但是会受到设备总内存(RAM 以及 SWAP 区)大小以及处理器寻址空间的限制。
- 通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作,这样能避免在 Java 堆和 Native 堆中来回复制数据。
- 默认容量与Java堆最大值一样
栈帧
- 方法的调用链路由Java一个个方法栈帧组成
- 一个栈帧包括了局部变量表、操作栈、动态连接、返回地址
- 局部变量表中的数据在函数调用结束后随着栈帧的销毁而销毁
- 操作数栈保存计算过程的中间结果,临时变量存储的地方
- 动态连接指执行运行时常量池中该栈帧所属方法引用
- 返回地址:正常完成出口,异常完成出口,无论是哪种推出方式,都要返回到方法被调用位置,才能继续执行
可达性算法
- 在主流的商用程序语言(Java、C# 和 Lisp 等)的主流实现中,都是通过可达性分析(Reachability Analysis)判定对象是否存活的。
- 当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。
引用相关
-
在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用和虚引用四种,这四种引用强度按顺序依次减弱。
-
强引用是指代码中普遍存在的,比如 "Object obj = new Object()" 这类引用。直接访问,不会回收(即使OOM),可能内存泄漏
-
对于软引用关联的对象,在系统即将发生内存溢出前,会把这些对象列入回收范围中进行二次回收。如果二次回收后还没有足够的内存,就会抛出内存溢出异常。在 JDK 1.2 后,Java 提供了 SoftReference 类来实现软引用。(这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。)
SoftReference<String> sr = new SoftReference<String>(new String("hello"));
-
弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。(biss)如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象
WeakReference<String> sr = new WeakReference<String>(new String("hello"));
-
虚引用在java中用java.lang.ref.PhantomReference类表示。
-
虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
-
当我们操作不当导致某块内存泄漏时,GC 就不能对这块内存进行回收。
-
拿 Android 来说,进行 GC 时,所有线程都要暂停,包括主线程,16ms 是 Android 要求的每帧绘制时间,而当 GC 的时间超过 16ms,就会造成丢帧的情况,也就是界面卡顿。
垃圾回收算法
-
标记-清除算法(Mark-Sweep) 标记所有商品的状态没人买的就下架 :效率低,标记和清除的效率都不高;内存碎片太多会导致当程序需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发 GC
-
复制(Copying)收集算法 可用内存按容量划分为大小相等的两块,这块内存用完了,就把存活的对象复制到另一块内存上,然后把已使用的空间一次清理。:每次都是对半个内存区域进行回收,内存分配时也不用考虑内存碎片等复杂问题。
-
浪费空间
把内存缩小一半来使用太浪费空间。
-
有时效率较低
在对象存活率高时,要进行较多的复制操作,这时效率就变低了