手记

对 Java 内存的一些理解-[Android_YangKe]

  • Java 垃圾回收机制优点

  • Java 内存模型

  • 什么是内存抖动

  • 什么是内存泄漏

  • 垃圾回收机制常见算法


Java 垃圾回收机制优点

垃圾回收机制是 Java 的特性之一,此特点很大程度上减少了研发人员的工作量。 最直接的感受就是工作中我们无需关注对象内存何时释放,只需尽情的忙于我们的业务,写代码简直不能太爽,C、C++ 等等都很羡慕的。方向很重要,一个来自同行的微笑 。


Java 内存模型

Java 内存模型共分为五类:

  • 方法区

  • 程序计数器

  • 本地方法栈

为了便于对内存模型有清晰的认识,下面我们来看一张图:


yangke.jpeg

堆:
Java 中的堆主要用来存储 Class 实例本身。举个简单例子现在有一个 Class 实例汽车Car,代码如下:Car car = new Car (); 其中 Car 实例存放在堆内存中,引用变量 car 存放在栈中。

研发中我使用最多的就是 new 关键字,通过上面例子我们知道通过此关键字创建的对象存放在堆内存中。堆对于我们来说操作很频繁。举个简单例子: 内存抖动、内存泄漏、OOM。

堆特点:
CPU 操作此内存模块由于需要先从栈中找到引用变量,然后去堆内存中找 Class 实例,最后进行业务操作,相对来说堆操作慢于栈操作。

栈:
栈中存放的是一个栈帧,每个栈帧对应一个被调用的方法。栈帧中包括局部变量表,操作数栈,当前方法所属类的运行时常量池的引用、方法返回地址和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。

栈特点:
基本数据类型、局部变量都是存放在栈内存中的,用完就消失。CPU 在执行程序的过程中如用到基本变量、局部变量直接去栈中进行获取,无需对堆、方法区等其他内存区域进行访问,所以栈操作相对来说速度更快。

方法区:
方法区是 JVM 中非常重要的一个区域,它与堆同等重要,是被线程共享的区域。方法区中存储了每个类的信息(包括类的名称,方法信息,字段信息)、常量、静态变量以及编译器编译后的代码等。

在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到 JVM 后, 对应的运行时常量池就被创建出来。

特点:
是一块特殊内存区域,在类加载的过程中此区域就会被创建。与堆的关联性比较强,主要体现在当我们创建一个 Class 实例后,此 Class 实例所对应的常量、静态变量都会创建。

程序计数器:
在了解程序计数器前我们先来了解一个概念 —— 指令。

指令:
指令一般指的就是计算机指令,是机器工作的指示和命令。程序就是一系列、一定顺序排列的指令,执行程序的过程就是计算机的工作过程。CPU 按照指令顺讯指挥机器工作,人们用指令表达自己的意图,并交给控制器执行。

程序计数器就是用来记录指令的 —> 内存,是 CPU 上一块特殊的内存区域。

特点:
程序计数器是一块特殊的内存区域,操作的都是二进制数据、与 CPU 更近,响应速度相对内存来说更快。

本地方法栈:
本地方法栈这里指 C、C++ 方法栈,作用和 Java 栈类似,只不过本地方法栈更偏底层。

特点:
由于语言更偏机器语言,处于操作系统级别,其特点主要体现在:性能高、操作性差,与上层通信不方便。


内存抖动

短时间内内存进行大量的申请与释放脑补下波浪图。常见原因:循环或者递归中编码不当导致大量内存申请与释放。

特点:
由于内存短时间内波动幅度比较大,空闲内存就会很有限,有卡顿、OOM 风险。


内存泄漏

无用对象无法被垃圾回收机制回收。常见情况:生命周期较短的对象持有生命周期较长对象的引用。

特点:在内存一定的情况下,内存泄漏会导致可用内存越来越少,导致程序卡顿、OOM。


垃圾回收常见算法

引用计数
就是对于创建的每一个对象都有一个与之关联的计数器,这个计数器记录着该对象被使用的次数,垃圾收集器在进行垃圾回收时,对扫描到的每一个对象判断一下计数器是否等于 0,若等于 0,就会释放该对象占用的内存空间,同时将该对象引用的其他对象的计数器进行减一操作。

特点:
算法实现相对简单,主要是对变量使用次数的加加减减,性能比较好,但无法解决无用对象循环引用问题。

标记回收:
在此阶段中,垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标记为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作。在垃圾回收阶段,应用程序的执行会暂停,等待回收执行完毕后,再恢复程序的执行。

特点:
垃圾收集后会造成大量的内存碎片,是引用计数回收算法的一种升级,同时可回收引用计数为 0 的对象。

分代回收
根据对象存活周期的不同将堆内存分块。一般把 Java 堆分为新生代,老年代和持久代,然后根据各个代的特点采用适合的回收算法。

新生代:
堆内存中一块较小的内存区域,主要用来存放生命周期较短的对象。回收频率高。

老年代:
堆内存中一块较小的内存区域,主要用来存放生命周期较长的对象。回收频率低。

持久代:
堆内存中一块较小的内存区域,主要存放 class 信息。此区域不参与垃圾回收。

在新生代,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对他们进行分配担保,就必须使用“标记整理”算法进行回收。

复制算法:
将内存空间分成两块,同一时刻只使用其中的一块,在垃圾回收时将正在使用的内存中的存活的对象复制到未使用的内存中,然后清除正在使用内存块中所有对象。

特点:如果存活对象较多的话,算法内部会对存活对象进行标记、整理、移动效率会降低,同时这样会使内存空间折半,但这种方法不会产生内存碎片。

以上就是对Java 内存的一些理解,完!


作者:Android_YangKe
链接:https://www.jianshu.com/p/97a7ead430d4

0人推荐
随时随地看视频
慕课网APP