转载

【拒绝一问就懵】之你多少要懂点内存回收机制

;

程序计数器是线程私有的内存区域,这个区域是Java虚拟机中唯一一个没有限制 OutOfMemoryError 的内存区域。之所以需要它是因为Java的多线程机制是通过轮流切换分配处理器执行时间来实现的,所以会涉及到线程的暂停和重启,而在一个线程中如果正在执行Java方法的话,这个计数器就回去记录当前正在执行的虚拟机字节码,一旦被暂停,恢复只需要从程序计数器记录的为止继续执行就可以。

但是,如果线程中执行的是一个Native方法,那么程序计数器是不会去记录的,所以此时的程序计数器为空。

虚拟机栈Stack

Java虚拟机栈也是线程私有的。一条线程启动就会为它建立一个虚拟机栈。

在线程中每有一个Java方法被调用就会创建一个 “栈帧” 。每个 “栈帧” 会保存执行该方法所需的局部变量表(一般Java程序员喜欢用这个部分来代表栈)、操作数栈、动态链接以及方法出口等信息。

如果一个线程中有过多的 “栈帧” 要入到虚拟机栈中,即短时间内调用了过多的方法,就会造成 -- 栈益处 -- ,即 StackOverflowError 错误。

在这个内存区域中,如果虚拟机需要扩展内存,但没有申请到足够的内存,就会抛出 OutOfMemoryError 错误。

本地方法栈

和虚拟机栈有些类似,但它是为Native方法提供服务的。

Java堆Heap

Java的堆内存是Java虚拟机所管理的内存中最大的一块。它是所有线程所共享的,用于存放对象实例和数组,Java虚拟机的GC主要就发生在这个地方。因此这块区域也叫做"GC堆"。

Java堆的内存可以按照垃圾回收算法【分代回收】分为【新生代区】和【老年区】,进一步的,【新生代区】可以分为【Eden区】、【From Survivor区】和【To Survivor区】。

从内存角度来说,Java堆内存又被划分为线程共享的内存区域和每个线程私有的内存区域。

在Java堆区域中,如果没有内存分配给要创建的实例,并且堆也不能够再扩展,就会抛出 OutOfMemoryError 错误。

回收算法

Java8 之后, Heap Segment 真正意义上的是由 Young GeneriationOld Generiation 组成的。对象在其中是 标记复制算法 来判定一个对象是否应该被清理掉。

Heap Segment 中发生的GC称为 Major GC ,只会影响 Heap Segment 区。

【拒绝一问就懵】之你多少要懂点内存回收机制

Young Generiation中的GC变化 — 复制算法

这个区域发生的GC称为 Minor GC

  • 当对象被创建后,首先会被加入 eden 区。当 eden 区满了之后,就会触发一次GC,存活下来的对象会被复制到 survivor 区。
  • 当不为空的 Survivor 区满了,同样会触发一次GC。
  • 当短时间内有大量对象创建和释放同样会造成 内存抖动 ,会触发CG。
  • 如图所示, survivor 有两个区域,其中一个总是保持为空。
  • 现假设两个 Survivor 区分别为S0,S1,并且首次GC时, eden 区中存活的对象被复制到S0中。当再次发生GC时,S0和 eden 中仍然存活的对象就会被复制到空的S1中,此时S0为空;再次发生GC时,S1和 eden 中存活的对象将被复制到S0中,此时S1为空;再次发生GC...就是这样进行的。当一个对象被来回复制转移的次数达到阀值(默认为15次,可以通过使用 -XX:MaxTenuringThreshold 该命令来调整阀值)时,这个对象将被复制到 Old Generiation 区中,此时该对象将会变的相对安全,因为 Old Segment 区的GC频率相对较低。

Old Segment中的GC变化

这个区域发送的GC成为 Full GC

  • 该区域满了之后会触发一次GC,在该次GC中,一些年龄较大的对象会被清理掉。
  • 若多次触发GC后,该区域仍然处于满的状态,则会抛出 OutOfMemoryError
  • 以两种情况下,新建对象会被直接复制到该区域中:
    • 当新建对象所需要的内存大于1/2的单个 survivor 区内存时。比如一些很长的对象;
    • 当新建对象被该区中的对象引用时,或者引用了该区域中的对象。

方法区

Java的方法区和Java的堆内存一样是被线程所共有的。它主要存放虚拟机加载的类信息、常量、静态变量、即时编译产生的代码等。

一些地方会将方法区合并到Java堆中一起去说。把它作为“永久代”。这在Hot-Spot虚拟机而言成立,但是一般来说是不成立的。

Java的方法区如果内存不够分配的话,也是会抛出 OutOfMemoryError 错误的。也就是如果加载过多类到方法区的话,可能会造成方法区内存益处。

对象的可到达性

在GC检查对象的是否可以回收时,是根据对象是否可到达引用练顶端的 GC Roots 对象来判断的。 GC Roots 对象一般是虚拟机栈中变量表中引用的对象、类静态属性引用的对象、常量对象、JNI传到底层的对象。就是说,一个对象如果溯源不到这几种类型的对象的话,就认为它是无法到达的,那么它将会在GC时被回收。

新的引用类型

在JDK 1.2之后,Java扩充了4种引用类型定义:

强应用类型

即我们平时通过new关键字创建出来的的对象的引用,只要强引用还存在,那么这些对象就一定不回被回收,即使时抛出 OutOfMemoryError 。什么时候强引用会不存在呢?当一个方法执行完,栈帧中的变量表将会被清理,在该方法中创建使用的临时强引用就会被清理掉,之后,原本它指向的对象就被变的不可到达。

软引用类型

用来描述一些有用但不是必须的对象,即通过 SoftReference 创建的对象,它们将会在原本确定要发生内存溢出前的一次GC中被回收,如果回收完内存还是不够,Java堆就会抛出 OutOfMemoryError 错误。就是说,在触发内存溢出发生前,这些对象是和强引用一样,只要引用还在,就不会被回收。

弱引用类型

用来描述一些不必须的对象,即通过 WeakReference 创建的对象。弱引用对象的生命周期只有一次GC。

虚引用类型

一个对象的存在与否完全不受虚引用的影响,它唯一的用处就是可以用来监测一个对象是否被回收。

方法区中的-运行时常量池

运行时常量池主要存放类中编译时期生成的常量,当然也可以动态的往里面添加。

比如:

"abc".intern();
复制代码

这个方法首先会检查运行时常量池中是否有这个字符串,有的话取出来用,没有的话生成一个并存到常量池中。

再比如,运行过程中生成通过 static 修饰的String时,也会加入到常量池中。对于String而言, 常量 + 常量 生成的也是常量,但是 常量 + 变量 生成的就是变量了。

关于Dalvik虚拟机

Dalvik虚拟机是Google按照JVM虚拟机规范定制的虚拟机,它更符合移动设备的环境要求。与标准虚拟机不同:

  • Dalvik编译生成的是 .dex 文件,这种格式的文件体积更小。而JVM规范的是 .class 文件。
  • Dalvik虚拟机是基于寄存器的,而JVM规范是基于栈的,所以速度方面会有优势。比如上面的说的标准Java虚拟机中,它的虚拟机栈就为线程的运行提供了服务。而Dalvik虚拟机中,使用寄存器去储存运行指令,同时寄存器也提供了程序计数器。寄存器是处理器的一部分哦!
  • Dalvik虚拟机允许在内存中创建都个实例,以隔离不同的应用程序。这样,当一个应用程序在自己的进程中崩溃后,不会影响其它进程的运行。
原文  https://juejin.im/post/5cdc0faf51882568666dfe2f
正文到此结束
Loading...