Java虚拟机笔记

时间:2022-12-29 09:37:54

Java内存区域划分

1、程序计数器。当前线程所执行字节码的行号指示器,程序的分支、循环、跳转、异常、线程恢复等都需要依赖程序计数器。
2、Java虚拟机栈。线程私有,线程运行时创建的栈帧,用于存储局部变量、动态连接、方法出口等信息
3、本地方法栈。类似于Java虚拟机栈,不过用于执行本地(native方法)方法
4、Java堆。线程共享,Java虚拟机规范规定:所有对象和数组都要在堆中分配
5、方法区。线程共享,主要用于存储类的信息、常量、静态变量
6、运行常量池。方法区的一部分,主要用于存储即时编译生成的各种字面量和符号引用

对象创建过程

1、虚拟机遇到一条new指令时,先去检查这个指令的参数能否在常量池中找到对应类的符号引用,并检查这个类的符号引用是否被加载、解析、和初始化过,如果没有,就执行加载和初始化过程
2、在类加载检查通过后,虚拟机将为新生对象分配内存。
    分配内存有两种方法:一、指针碰撞。如果内存中已经使用的内存放在一边,未使用过的内存放在一边,中间用一个指针作为分界点的指示器,则将指针向未使用内存一方偏移对象大小的距离,称为指针碰撞。二、空闲列表法。如果内存不连续,则虚拟机中会维护一个空闲空间内存地址的列表,虚拟机会在这个列表中找到一块足够大的内存分配给新的变量,称为空闲列表法。
    那如何保证内存分配的安全性呢?一、对内存空间的分配动作采用同步处理。事实上,虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。二、对每个线程,在Java堆中预先分配一小块内存,称为本地线程分配缓冲TLAB,以此来隔离各个线程的内存分配,使得它们互不干扰

3、内存分配完成后,虚拟机将分配到的内存空间都初始化为零
4、对对象进行必要的设置。如:类的元数据、对象的哈希码、对象的GC分代信息等
5、调用对象的init方法,执行初始化,完成对象创建

对象的内存布局

1、在HotSpot虚拟机中,对象分内存分为:对象头、实例数据和对齐填充三个部分

对象头包含两个部分,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、偏向线程ID、偏向时间戳等;
第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过该指针确定对象是哪个类的实例

实例数据用于存储类真正的有效信息,即程序代码中定义的各种类型的字段内容

对齐填充,这部分并不是必然存在,也没有特别意义,仅仅是起着占位符的作用

对象访问

Java程序是通过栈上的reference数据来操作具体对象的。目前主要的对象访问方式有两种:
一、句柄访问。reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据、类型数据各自的具体地址信息
二、直接访问。reference中直接存储对象的地址

如何判断对象是否存活

1、引用计数法:给对象添加一个引用计数器,每当一个地方引用它时,计数器加1;当引用失效时,计数器减1,当计数器为0,表示该对象不再被使用,应该回收。
优点是:实现简单,判断效率高。缺点是:很难解决对象之间互相循环引用的问题。例如objA.instance=objB;objB.instance=objA;其他地方再无引用这两个变量,但是因为它们互相引用着对方,因而计数器都不为零,导致GC无法回收它们
实际上,Java的大多虚拟机实现都没有采用上面这种方法

2、可达性分析法:通过一系列称为GC Roots的对象作为起点,从这些起点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链,证明该对象不可用,应该被回收

Java中的引用

引用:如果reference类型的数据中存储的数值代表另一块内存中的起始地址,就称这块内存代表着一个引用
引用分为强引用、软引用、弱引用、虚引用四种
强引用:如果对象存在强引用,就不能被垃圾收集器回收,入Object obj = new Object();
软引用:用来描述一些还有用但并非必须的对象。在系统发生内存溢出之前会将这些对象回收
弱引用:用来描述一些非必须的对象,比软引用更弱一些。被弱引用关联的对象只能存活到下一次垃圾回收之前
虚引用:也称为幽灵引用,是最弱的一种引用关系。一个对象有虚引用,完全不会对其生命周期产生任何影响,也不能通过虚引用来创建对象实例,为对象设置虚引用的目的是在对象被垃圾回收之前获得一个通知

垃圾回收算法

为什么要了解垃圾回收?当要排查各种内存泄漏、内存溢出时,当垃圾回收变成系统高并发的瓶颈时,我们需要对这些自动化技术进行监控
1、标记-清除算法
    基本思想:首先标记出需要回收的对象,然后在标记完成之后统一回收被标记的对象。
    不足之处:一、标记和清除两个过程效率都不高;二、空间问题,标记清除后会产生大量不连续的空间,形成空间碎片,导致后面的大对象可能无法找到一个足够大的连续内存,从而触发提前GC

2、复制算法
    基本思想:将内存分为大小相等的2块,当一块内存用完,就将还存活的对象复制到另一块内存,再把使用过的内存一次全部清理掉
    优点:不存在内存碎片;内存分配时只需要移动指针即可,实现简单,运行高效
    缺点:内存浪费,内存空间只有一半的利用率;在对象存活率高时,复制效率将会比较低
    主要用于新生代,因为新生代中,正常80%的对象都是朝生夕死的,所以可以采用8:1:1的比例来划分内存,用两个较小的内存来存放第一次回收时没有被回收掉的对象

3、标记-整理算法
    基本思想:类似于标记-清理算法;先将可回收的对象标记出来,然后将所有存活的对象都复制到另外一边,之后清理掉另一边的所有内存
    
4、分代收集算法
    基本思想:根据对象存活周期的不同将内存划分为几块,然后对不同的区域采用不同的收集算法。
    通常将内存划分为新生代和老年代。新生代中,每次垃圾回收时都会有大量的对象死去,可选用复制算法,每次都可以用少量的复制成本完成收集;对于老年代,对象存活率高,可采用标记-清理算法或则标记-整理算法

类的加载机制

类的加载时指虚拟机把描述类的class文件加载到虚拟机内存,并对数据进行校验、转换解析和初始化,最终形成虚拟机可以直接使用的Java类型的过程