方法区
? 属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
java虚拟机:
? 线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表
、操作数栈
、动态链接
、方法出口
等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
本地方法栈
? 区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。
java堆
? 对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。
程序计数器
内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成
运行时常量池
? 属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。
直接内存
? 非虚拟机运行时数据区的部分
在 HotSpot 虚拟机中,分为 3 块区域:
对象头(Header)
第一部分用于存储对象自身的运行时数据,
哈希码
GC 分代年龄
锁状态标志
线程持有的锁
偏向线程 ID
偏向时间戳等
32 位虚拟机占 32 bit,64 位虚拟机占 64 bit。官方称为 ‘Mark Word’。
第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例。另外,如果是 Java 数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以。
实例数据(Instance Data)
程序代码中所定义的各种类型的字段内容(包含父类继承下来的和子类中定义的)
对齐填充(Padding)
不是必然需要,主要是占位,保证对象大小是某个字节的整数倍。
句柄访问,通过引用数据来操作堆上的具体对象,java堆中会分配一块内存作为句柄池,引用存储的是句柄地址。
指针访问,引用中直接存对象地址
比较
回收内存之前首先需要判断那些对象是已经死亡的
引用计数法,给对象添加一个引用计数器。但是难以解决循环引用问题。
可达性分析法,通过一系列的 ‘GC Roots’ 的对象作为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用。
可作为 GC Roots 的对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象
? 对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。没必要执行finalize()表示该对象没用了,可以标记准备回收
? 如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束。
? finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象简历关联即可。若未连接,则回收。
? finalize() 方法只会被系统自动调用一次。
引用
强引用:类似于 Object obj = new Object();
创建的,只要强引用在就不回收。
虚引用:PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
回收方法区
标记---清除:效率不高,产生大量碎片
复制:将空间分为2块,每次对一块gc,将活的对象移动到另一块
标记--整理算法
? 不同于针对新生代的复制算法,针对老年代的特点,创建该算法。主要是把存活对象移到内存的一端。
分代回收
? 根据存活对象划分几块内存区,一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法。
新生代:复制算法
老年代:标记清除,标记整理
serial收集器:这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。
ParNew:多线程serial
serial old :收集器的老年代版本,单线程,使用 标记 —— 整理
。
Parallel Scavenge 收集器:这是一个新生代收集器,也是使用复制算法实现,尽可能缩短用户等待时间。
parallel old:Parallel Scavenge 收集器的老年代版本。多线程,使用 标记 —— 整理
CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记 —— 清除
算法实现。
缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记 —— 清除
算法带来的空间碎片
G1 收集器:面向服务端的垃圾收集器
优点:并行与并发、分代收集、空间整合、可预测停顿。
运作步骤:
![
操作 | 对象 | 解释 |
---|---|---|
lock | 主内存 | 把一个变量标识为一条线程独占的状态 |
unlock | 主内存 | 把一个处于锁定状态的变量释放出来,释放后才可被其他线程锁定 |
read | 主内存 | 把一个变量的值从主内存传输到线程工作内存中,以便 load 操作使用 |
load | 工作内存 | 把 read 操作从主内存中得到的变量值放入工作内存中 |
use | 工作内存 | 把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量值的字节码指令时将会执行这个操作 |
assgin | 工作内存 | 把一个从执行引擎接收到的值赋接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作 |
store | 工作内存 | 把工作内存中的一个变量的值传送到主内存中,以便 write 操作 |
write | 工作内存 | 把 store 操作从工作内存中得到的变量的值放入主内存的变量中 |
关键字 volatile 是 Java 虚拟机提供的最轻量级的同步机制。
volatile变量
? Java 要求对于主内存和工作内存之间的八个操作都是原子性的,但是对于 64 位的数据类型,有一条宽松的规定:允许虚拟机将没有被 volatile 修饰的 64 位数据的读写操作划分为两次 32 位的操作来进行,即允许虚拟机实现选择可以不保证 64 位数据类型的 load、store、read 和 write 这 4 个操作的原子性。这就是 long 和 double 的非原子性协定。
直接使用内核线程
? 直接由操作系统内核支持的线程,这种线程由内核完成切换。程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口 —— 轻量级进程(LWP),轻量级进程就是我们通常意义上所讲的线程,每个轻量级进程都有一个内核级线程支持。
用户线程
? 广义上来说,只要不是内核线程就可以认为是用户线程,因此可以认为轻量级进程也属于用户线程。狭义上说是完全建立在用户空间的线程库上的并且内核系统不可感知的。
使用用户线程夹加轻量级进程混合实现
其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的。解析阶段可以在初始化之后再开始(运行时绑定或动态绑定或晚期绑定)。
以下五种情况必须对类进行初始化(而加载、验证、准备自然需要在此之前完成):
加载
验证:是连接的第一步,确保 Class 文件的字节流中包含的信息符合当前虚拟机要求。
准备:这个阶段正式为类分配内存并设置类变量初始值,内存在方法去中分配(含 static 修饰的变量不含实例变量)。
public static int value = 1;
这句代码在初始值设置之后为 0,因为这时候尚未开始执行任何 Java 方法。而把 value 赋值为 1 的 putstatic 指令是程序被编译后,存放于 clinit() 方法中,所以初始化阶段才会对 value 进行赋值。
char=‘\u0000‘ boolean=false
解析:这个阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行,分别对应于常量池的 7 中常量类型。
初始化:前面过程都是以虚拟机主导,而初始化阶段开始执行类中的 Java 代码。
通过一个类的全限定名来获取描述此类的二进制字节流。
双亲委派模型
从 Java 虚拟机角度讲,只存在两种类加载器:一种是启动类加载器(C++ 实现,是虚拟机的一部分);另一种是其他所有类的加载器(Java 实现,独立于虚拟机外部且全继承自 java.lang.ClassLoader)
除顶层启动类加载器之外,其他都有自己的父类加载器。
工作过程:如果一个类加载器收到一个类加载的请求,它首先不会自己加载,而是把这个请求委派给父类加载器。只有父类无法完成时子类才会尝试加载。
破坏双亲委派模型
keyword:线程上下文加载器(Thread Context ClassLoader)
原文:https://www.cnblogs.com/Dean0731/p/12219335.html