简介JVM

目录

一、内存分区

1、程序计数器 

2、栈

3、堆

4、方法区 

二、类加载 

1、Loading 

2、Linking 

Verification 

Preparation 

Resolution 

3、Initializing

4、双亲委派模型 

三、垃圾回收 

1、如何判断为垃圾? 

引入引用计数

可达性分析

2、如何进行垃圾回收? 

标记清除

复制算法 

标记整理 

分代回收 


一、内存分区

Java将内存分为了程序计数器、栈区、方法区、堆区。

1、程序计数器 

程序计数器是内存中最小的区域,保存了下一条要执行指令的地址。

程序运行时,JVM就会把字节码加载起来,放到内存中,程序把指令从内存中取出来,放到CPU上执行这也就需要随时记住当前指令执行的位置,因为CPU是并发执行程序的,并且操作系统以线程为单位进行调度执行的,所以每个线程都有程序计数器。

2、栈

栈区主要保存的是局部变量和方法的调用信息,方法调用的时候就会涉及“入栈”操作,方法执行完之后就会涉及“出栈”的操作,局部变量也是类似。

栈空间也是比较小的,代码处理不当就会出现栈溢出异常。

每个进程都会分配一个栈区。

3、堆

堆区是内存空间中最大的区域,每次new出来的对象存在于堆区,对象的成员变量也就自然存在于堆区了。

一个进程会分配一个堆区,多个线程共享一个堆区。

4、方法区 

方法区存放的是类对象,每次当.class文件编译形成.class文件,.class文件会被加载到内存中,也就会被JVM构造成类对象(加载的过程就是类加载),此处的类对象就会存放到方法区,类对象中的静态成员也会存放到方法区。

二、类加载 

类加载是将.class文件加载到内存中,构建成类对象,类加载主要有Loading、Linking和Initiallizatio三步。

简介JVM_第1张图片

1、Loading 

Loading过程是先找到对应的.class文件,然后打开并读取.class文件,会将读取并解析到的信息初步填写到类对象中,同时会初步生成一个类对象。

2、Linking 

Linking来建立多个实体之间的联系,Linking又可以分为Verification、Preparation和Resolution三个阶段。

Verification 

校验阶段,主要来验证读到的内容与规范中规定的格式是否完全匹配,如果返现格式不匹配,就会类加载失败,并且抛出异常。

Preparation 

准备阶段,是正式为类中的变量分配内存并设置初始值,也就是给静态变量分配内存,并设置初值。

Resolution 

解析阶段是Java虚拟机将常量池中的符号引用替换为直接引用的过程,也就对常量初始化的过程。

3、Initializing

是真正对类对象进行初始化,尤其是对静态成员

4、双亲委派模型 

双亲委派模型其实是Loading的一个环节,描述的是JVM中的类加载器如何根据类的全限定名来找到.class文件的过程,也就是描述了找目录的过程。

JVM中提供了许多类加载器,每个类加载器负责一个片区,默认的类加载器主要有三个:

  • BootStrapClassLoader:负责加载标准库中的类。
  • ExtensionClassLoader:负责加载JDK扩展的类。
  • ApplicationClassLoader:负责加载当前项目目录中的类。

查找标准库中的类的过程: 

  1. 启动程序,先进入ApplicationClassLoader类加载器。
  2. ApplicationClassLoader就会检查它的父类是否已经加载过了,如果没有就调用父加载器ExtensionClassLoader。
  3. ExtensionClassLoader也会检查它的父类是否已经加载过了,如果没有就调用父加载器BootStrapClassLoader。
  4. BootStrapClassLoader会扫描自己负责的目录,一定会找到,找到后负责类加载的后序过程,直至查找环节结束。

查找自定义类的过程:

  1. 启动程序,先进入ApplicationClassLoader类加载器。
  2. ApplicationClassLoader就会检查它的父类是否已经加载过了,如果没有就调用父加载器ExtensionClassLoader。
  3. ExtensionClassLoader也会检查它的父类是否已经加载过了,如果没有就调用父加载器BootStrapClassLoader。
  4. BootStrapClassLoader会扫描自己负责的目录,如果没找到,就回到子加载器ExtensionClassLoader继续进行扫描。
  5. ExtensionClassLoader也会对自己负责的目录进行扫描,如果没找到,就回到子加载器ApplicationClassLoader继续进行扫描。
  6. ApplicationClassLoader对自己负责的目录进行扫描,如果找到就进行后续加载,否则就会抛出ClassNotFoundException异常。

三、垃圾回收 

如果一直申请空间而不进行空间回收的话就会导致内存用完,也就是内存泄露的问题,对于栈和程序计数器不考虑垃圾回收,当方法或进程结束时所占用的空间也会随着释放,垃圾回收主要针对堆和方法区。

1、如何判断为垃圾? 

引入引用计数

就是给对象再开辟一块区域用于引入计数器,当调用这个对象时,计数器就会加1,当引用失效的时候,计数器就会-1,如果计数器为0,该对象就会变为“垃圾”,进行回收。

但是也存在缺点:

空间利用率低,当对象本身空间很小时再增加一个计数器空间利用率低。

循环引用

public class T {
    public T t = null;

    public static void main(String[] args) {
        T t1 = new T();
        T t2 = new T();
        t1.t = t2;
        t2.t = t1;
        t1 = null;
        t2 = null;
    }
}

两个对象相互引用之后引用的t对象的程序计数器就为1,无法进行进行回收。

可达性分析

定义一个起始位置GCRoots,类似于深度优先遍历,对可以遍历到的对象进行标记,没标记的对象就是不可达,就成了垃圾。

例如:

简介JVM_第2张图片

GCRoots可以访问所有结点就不存在垃圾。

 简介JVM_第3张图片

 GCRoots无法访问C和F,C和F就是垃圾。

在Java语言中,可作为GC Roots的对象包含下面几种:

1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;

2. 方法区中类静态属性引用的对象;

3. 方法区中常量引用的对象;

4. 本地方法栈中 JNI(Native方法)引用的对象。

2、如何进行垃圾回收? 

标记清除

进行可达性标记,对于未标记的直接进行清除释放内存。

缺点:产生许多内存碎片。

复制算法 

为了避免产生内存碎片,就引入了复制算法,将申请的内存一分为2,将不是垃圾的拷贝到另一边,将原来使用的一半空间整体释放。

缺点:

  • 空间利用率低。
  • 当垃圾较少时,复制开销较大。

标记整理 

将不是垃圾的元素都拷贝到申请的空间的前面,后面的空间可以正常利用。类似于顺序表删除元素。

分代回收 

简介JVM_第4张图片

对象新创建出来的时候先放在伊甸园,当伊甸园中的对象熬过一轮GC扫描,利用复制算法就会被拷贝到幸存区,在后序的几轮GC中幸存区的对象还是利用复制算法在幸存区之间来回拷贝,每一轮又会进行淘汰,在持续若干次之后,对象就会进入老年代,对象越老,继续存活的可能性就越大,老年的扫描频率低,并且老年代使用标记整理的方式进行回收。

分代回收中占用内存较大的对象可直接进入老年代。

你可能感兴趣的:(JavaEE,jvm,java,开发语言)