JAVA虚拟机内存管理+GC回收内存

首先明确一点,JAVA虚拟机是个什么玩意?
很显然,先不去管JAVA的虚拟机,它是个虚拟机,那就是一个仿真计算机。

1、来看看其内存的访问操作:

在这里插入图片描述
也就是说,JAVA中使用多线程机制,使得多个线程同时执行不同任务,所有线程共享main memory主存,而每个线程又都有自己单独的工作内存,当线程与内存区域进行交互时,数据由主存拷贝到工作内存,进而交给线程处理。

2、我们再来看看JAVA虚拟机的逻辑内存模型:

在这里插入图片描述

  1. 方法区:是线程共享的内存区域,它用来存储已被系统加载的类信息,常量和静态变量,即时编译器编译后的代码等数据,在GC回收内存的分代算法中,方法区也叫永久代,也就是说方法区中这部分内存是很少被GC回收的,主要是针对常量池的回收和类型的卸载,条件非常苛刻(对类型的卸载)
  2. Java堆:是所有线程共享的内存区域,它是Java虚拟机管理的最大的一块内存,在虚拟机启动时创建,用来存储new的对象实例,Java堆是GC回收的主要区域,从内存回收的角度来看(如今大多数回收算法都是分代算法):Java堆可以细分为新生代,老年代,再细致一点:Eden 空间、From Survivor 空间、To Survivor 空间等。从内存分配的角度来说,线程共享的Java堆可以区分为多个线程分配缓冲区,根据Java虚拟机规范可知,Java堆上分配内存可以是物理上不连续的内存碎片,如果在Java堆中无法扩充,不能完成对象实例化的分配,就会抛出oom异常,
  3. Java虚拟机栈:创建线程时创建Java虚拟机栈,用来存储栈帧,所以它是线程私有的,当Java程序在执行时,会创建一个栈帧,用来存储方法运行时产生的临时数据和中间结果,这些数据包括:局部变量表、操作数栈、动态链接、方法出口等,局部变量表存放了编译期可知的八种数据基本数据类型,局部变量表所需的内存是在编译期间完成分配的,当进入一个方法时,局部变量表所需的内存大小是完全固定的,方法运行期间不会改变,根据Java虚拟机规范,在这个区域规定了两种内存异常:stackOverFlowError和oom,前者是线程请求栈深度大于jvm规定深度(递归会报这个异常),后者是程序扩展无法申请到足够的内存
  4. 程序计数器:它的作用是记录当前线程运行的位置,每个线程都有独立的程序计数器,其工作原理就是通过改变程序计数器的值来选取下一条需要执行的字节码指令,分支、循环跳转、异常处理、线程的阻塞、挂起、恢复都需要程序计数器的参与
  5. 本地方法栈:它的主要作用是处理JNI调用的Native方法,也有两种内存异常:stackOverFlowError和oom

3,GC回收机制

  • 哪些内存需要回收–who
  • 什么时候回收 --when
  • 怎么回收 --how
    ①哪些内存需要回收?
    线程私有的有:程序计数器、jvm栈、本地方法栈(随线程的生命周期,线程分配多少内存是有定数的,当线程被销毁时,内存就会释放)
    线程共享的有:Java堆,方法区(内存都是动态分配的,所以也需要动态回收,这部分是GC回收的主要工作区域)
    ②什么时候回收?
    一个是引用计数法:给对象添加一个引用计数器,每当程序引用一次对象,计数器加一,反之每当一个引用计数器失效时,计数器减一,当计数器为0时,说明当前对象没有引用,举个例子:
Object obj = new Object();//计数器 +1 = 1
obj = null;//计数器 - 1 = 0,失效 GC回收
//但是当对象之间相互调用,引用计数器就无法使GC回收,
Object a = new Object(); // a的引用计数为1 
Object b = new Object(); // b的引用计数为1 
a.next = b; // a的引用计数为2 
b.next = a; // b的引用计数为2 
a = null; // a的引用计数为1,尽管已经显示地将a赋值为null,但是由于引用计数为1,GC无法回收a
b = null; // b的引用计数为1,同理,GC也不回收b

一个是可达性分析:设立若干根对象(GC Root) ,每个对象都是一个子节点,当一个对象找不到根节点,也就是无人引用时,标志其不可达,
可以作为GC Root的对象包括:

  1. jvm栈中引用的对象
  2. 方法区中静态变量引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中引用的对象
    ③怎么回收?
    标记 - 清除算法(现在升级的算法是标记 - 整理算法)、复制算法、分代算法
    一:标记清除算法,就是遍历GC Root,标记可达不可达对象,然后回收不可达对象,这种算法缺点是效率低,无法回收连续物理内存,后来升级为标记 - 整理算法,将可达对象移动到内存的一端,然后GC回收剩下部分连续的物理内存。
    二:复制算法,最初是将内存分为相等的两块,只用其中的一块,当这块内存满的时候,将其中存活的对象复制到另一块内存中,然后GC 回收释放之前这块内存。
    三:分代算法,在Java中,将内存中的对象按照生命周期长短分成
  5. 新生代,活不了多久就死的对象,比如局部变量,用复制算法
  6. 老年代 ,生命周期长的对象,活的久不过也是会死的,用标记清除算法
  7. 永久代 -----基本上GC不回收

猜你喜欢

转载自blog.csdn.net/dream_caoyun/article/details/82852070