Java的内存划分全解析

时间:2022-12-17 10:45:19 JAVA认证 我要投稿
  • 相关推荐

Java的内存划分全解析

  Java把内存划分成两种:一种是栈内存,一种是堆内存。以下是小编整理的Java的内存划分全解析,希望对大家有所帮助。

  栈内存

  存放对象:函数中基本类型的变量和对象的引用变量、静态类方法

  特点:栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

  inta=3;

  intb=3;

  编译器先处理inta=3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。

  接着处理intb=3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。

  堆内存

  存放对象:用来存放由new创建的对象和数组。

  特点:在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。

  在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。

  引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。

  Q:static关键字,是一个修饰符,用于修饰成员(成员变量和成员函数),有什么特点

  A:1、想要实现对象中的共性数据的对象共享。可以将这个数据进行静态修饰。

  2、被静态修饰的成员,可以直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方式。

  3、静态随着类的加载而加载。而且优先于对象存在。

  Java内存回收

  Java的内存分配和回收也主要在Java的堆上进行的,Java的堆中存储了大量的对象实例,所以Java的堆也叫GC堆。

  下面主要说一下对于java堆的内存回收 。

  什么样的内存可以回收

  判断法1:引用计数

  方法:每有一个引用指向这个对象,那么这个对象的引用计数+1,反之,每有一个引用改变了指向,那么他原来指向的对象引用计数-1,当引用计数为0的时候,这个对象也就不可能被使用了那么就可以被回收了

  问题:可能会出现环状的引用,导致不可能被使用的对象永远不可能被回收

  示例:

  Class A {

  A a;

  Public static void main(String[] args){

  A gc1 = new A();

  A gc2 = new A();

  Gc1.a = gc2;

  Gc2.a = gc1;

  Gc1= null;

  Gc2 = null;

  }

  Gc1和gc2都被设置成null了,他们都应该被清理,但是因为gc1的a对象指向gc2,gc2的a对象指向gc1,导致他们的引用计数永远为1,但是他们都永远不可能被使用了,所以这种方法存在漏洞

  判断2:可达性分析算法

  方法:从一个叫做GC ROOTS的节点出发,所有能够到达的引用对象标记起来,直到走到完全没有引用的地方为止,这样从这个节点连起来的所有的点(引用链),构成的路线就是不可回收的,那么所有没有被到达过的对象均可以被回收

  什么可以做GC ROOTS:虚拟机栈(栈帧中的本地变量表)中的引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象

  这些对象的特点:不可变并且随时可能被用到,生命周期长

  补充:可达性分析的算法,那么没有在引用链上的对象都一定会被清理吗?不一定。当运行可达性分析的算法之后,会对所有没有在引用链上的对象进行一次标记和筛选,筛选的条件为:该对象覆盖了finallize()方法(这个方法是GC的时候如果这个对象要被回收则执行的方法,但是在Thinking in java中不推荐用来处理收尾工作),并且这个方法没有被执行过,那么就会把这个对象放到一个低优先级队列中执行,也就是这个对象的最后抢救的机会,如果这个时候这个对象把自己和在引用链上的引用连了起来,那么他在执行完finallize方法之后,再次判断时就不会被清理,否则会在第二次可达性判断的时候直接清理(因为finallize已经执行过一次了),如果没有覆盖这个方法,那么对不起,再见

  回收算法介绍:

  回收算法1:标记清理(Mark——sweep)算法

  标记所有需要回收对象,然后将他们清理回收

  问题:会产生内存碎片

  优点:不需要暂停所有线程(Stop the world)

  回收算法2:复制

  标记后,将所有不需要回收的对象全部复制到一个空的内存中,然后清理刚刚使用的内存块

  问题:浪费资源,会有一些内存堆无法被使用

  解决:用在新生代,新生代会有80%以上的对象经过一次GC就会死亡,因此采用Eden + Survivor * 2的办法,Eden = 8 * Survivor大小(HotSpot默认),那么每次使用一个Eden + 一个Survivor,然后进行复制清理的时候,清理Eden + Survivor中,然后将可用的对象复制到空闲的Survivor中,然后全部清空前面的使用区,然后使用Eden 和复制到的Survivor

  又一个问题:如果Survivor不够怎么办?向老年代借空间,叫做分配担保,不够存放的对象会通过分配担保进入老年代

  问题:需要 stop the world

  回收算法3:标记整理(Mark——compact)

  标记后,将可用内存向一侧摆放,然后清理掉可用内存边缘外部的所有内存区域

  优点:没有内存碎片的问题

  问题:需要stop the world

  回收算法4:分代收集

  将堆分代(老年代、新生代),老年代采用标记整理、标记清理等方法,新生代采用复制方法。

  为什么:因为老年代大部分对象是可用的,因此如果采用复制算法,虽然没有内存碎片,但是空间浪费大,而且大部分对象没有变化,而在新生代使用复制算法,可以牺牲很小的内存空间就获得较好的效率

  HotSpot中内存回收算法

  枚举根节点(GC ROOTs)

  Java虚拟机采用准确式GC,有一个OOPmap来标记哪个位置有个对象,这样在查找引用链的时候可以较快的找齐所有的引用链

  Safepoint

  在OOPmap的协助下,这个可以快速且准确的完成枚举,但是问题就是导致这个oopmap变化的指令非常多,如果为每一条指令生成oopmap,那么会需要大量额外空间,因此采用在特定点的地方生成,这些点同时也是safepoint的点,那么当需要枚举根节点的时候,就让线程运行到这个地方再停止。

  一种方法:先停止所有线程,然后再让没有到安全点的线程自己跑到安全点再停下来,基本不适用,抢先试中断

  另一种方法:主动式中断,设置一个标记,在执行的时候去轮询,而需要中断的时候,直接将这个位置的内存设置不可达,那么线程就会进入一个自陷异常,就自己会中断

【Java的内存划分全解析】相关文章:

客厅设计要点全解析03-16

JAVA垃圾收集算法与内存泄露的解决方法04-07

电脑内存优化技巧02-24

学习java技巧10-31

茶艺师的等级划分04-08

vlan划分方法介绍精选04-15

Java的基础知识07-27

酒店分类和等级划分11-07

空手道的段位划分02-11