<JVM>

JVM垃圾回收机制(GC)

一、怎样标识哪些对象“已死”?

既然名叫垃圾回收,那么哪些对象成为“垃圾”呢已经不再被使用的对象便视为“已死”,就应该被回收。在Java中,GC只针对于堆内存,Java语言中不存在指针说法,而是叫引用,在堆内存中没有被任何栈内存引用的对象应该被回收。

1.引用计数算法

给每一个对象加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能被使用的,即将被垃圾回收器回收。

缺点:无法解决对象间互相循环引用的问题。即当两个对象循环引用时,引用计数器都为1,当对象周期结束后应该被回收却无法回收,造成内存泄漏。

2.可达性分析算法

目前主流使用的都是可达性分析算法来判断对象是否存活。算法基本思路:以“GC Roots”作为对象的起点,从此节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
哪些对象可作为GC Roots?

虚拟机栈(栈帧中的本地变量表)中引用的对象;
方法区中类静态属性引用的对象;
方法区中常量引用的对象;
本地方法栈中JNI(Native方法)引用的对象;
活跃线程的引用对象。

二、Java中四种引用

在JDK1.2之前,Java中的引用定义很单一:如果reference类型的数据中储存的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。但是这种定义太过狭隘,如果某个对象介于被引用和未被引用两种状态之间,那么这种定义就显得无能为力。在JDK1.2后Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference),这四种引用强度依次逐渐减弱。

强引用(Strong Reference)

强引用就是值在程序代码中普遍存在的,用new关键字创建的对象都是强引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用(Soft Reference)

软引用是用来描述一些还有用但并非必需的对象,在系统将要发生内存溢出之前,将会吧这些对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。可用来实现高速缓存。软引用对象在回收时会被放入引用队列(ReferenceQueue)。

//  软引用SoftReference<String> softReference = new SoftReference<>("北风IT之路");

弱引用(Weak Reference)

弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次GC发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉该类对象。弱引用对象在回收时会被放入引用队列(ReferenceQueue)。

//  弱引用WeakReference<String> weakReference = new WeakReference<>("北风IT之路");

虚引用(Phantom Reference)

虚引用被称为幽灵引用或幻象引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得对象实例。任何时候都可能被回收,一般用来跟踪对象被垃圾收集器回收的活动,起哨兵作用。必须和引用队列(ReferenceQueue)联合使用。

//  虚引用,必须配合引用队列使用ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();PhantomReference<String> phantomReference = new PhantomReference<>("北风IT之路",referenceQueue);

三、finalize()赋予对象重生

在可达性分析算法中被标记为不可达的对象,也不一定是一定会被回收,它还有第二次重生的机会。每一个对象在被回收之前要进行两次标记,一次是没有关联引用链会被标记一次,第二次是判断该对象是否覆盖finalize()方法,如果没有覆盖则真正的被定了“死刑”。

如果这个对象被jvm判定为有必要执行finalize()方法,那么这个对象会被放入F-Queue队列中,并在烧毁由一个由虚拟机自动创建的、低优先级的finalizer线程去执行它。但是这里的“执行”是指虚拟机会触发这个方法,但是并不代表会等它运行结束。虚拟机在此处是做了优化的,因为如果某个对象在finalize方法中长时间运行或者发送死循环,将可能导致F-Queue队列中其他对象永远处于等待,甚至可能会导致整个内存回收系统崩溃。

注意!finalize()方法只会被系统调用一次,多次被gc只有第一次会被调用,因此只有一次的重生机会。

四、回收方法区

假如一个字符串“abc”已经进入了常量池中,但是当前系统没有任何一个String对象是“abc”,那么这个对象就应该回收。方法去(HotSpot虚拟机中的永久代)的垃圾收集主要回收两部分内容:废弃常量和无用的类。比如上述的“abc”就是属于废弃常量,那么哪些类是无用的类呢?

该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
加载该类的ClassLoader已经被回收;
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

五、垃圾收集算法

1.标记-清理算法(Mark-Sweep)

算法思路:算法分为“标记”和“清理”两个步骤,首先标记处所有需要回收的对象,在标记完成后再统一回收所有被标记的对象。

缺陷:
标记和清理的两个过程效率都不高;
容易产生内存碎片,碎片空间太多可能导致无法存放大对象。
适用于存活对象占多数的情况。

2.复制算法(Copy)

算法思路:将可用内存划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完后,就将还存活的对象复制到另一块去,然后再把已使用过的内存空间一次清理掉。

缺陷:
可用内存缩小为了原来的一半
算法执行效率高,适用于存活对象占少数的情况。

3.标记-整理算法(Mark-compact)

算法思路:标记过程和标记-清理算法一样,而后面的不一样,它是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,有效地避免了内存碎片的产生。

4.分代收集算法(Generational Collection)

当前大多数垃圾收集都采用的分代收集算法,这种算法并没有什么新的思路,只是根据对象存活周期的不同将内存划分为几块,每一块使用不同的上述算法去收集。在jdk8以前分为三代:年轻代、老年代、永久代。在jdk8以后取消了永久代的说法,而是元空间取而代之。一般年轻代使用复制算法(对象存活率低),老年代使用标记整理算法(对象存活率高)。

4.1 年轻代(复制算法为主)

尽可能快的收集掉声明周期短的对象。整个年轻代占1/3的堆空间,年轻代分为三个区,Eden、Survivor-from、Survivor-to,其内存大小默认比例为8:1:1(可调整),大部分新创建的对象都是在Eden区创建。当回收时,先将Eden区存活对象复制到一个Survivor-from区,然后清空Eden区,存活的对象年龄+1;当这个Survivor-from区也存放满了时,则将Eden区和Survivor-from区存活对象复制到另一个Survivor-to区,然后清空Eden和这个Survivor-from区,存活的对象年龄+1;此时Survivor-from区是空的,然后将Survivor-from区和Survivor-to区交换,即保持Survivor-from区为空(此时的Survivor-from是原来的Survivor-to区), 如此往复。年轻代执行的GC是Minor GC。

年轻代的迭代更新很快,大多数对象的存活时间都比较短,所以对GC的效率和性能要求较高,因此使用复制算法,同时这样划分为三个区域,保证了每次GC仅浪费10%的内存,内存利用率也有所提高。

4.2 老年代(标记-整理算法为主)

在年轻代经过很多次垃圾回收之后仍然存活的对象(默认15岁),就会被放入老年代中,因为老年代中的对象大多数是存活的,所以使用算法是标记-整理算法。老年代执行的GC是Full GC。

4.3 永久代/元空间

jdk8以前:
永久代用于存放静态文件,如Java类、方法等。该区域回收与上述“方法区内存回收”一致。但是永久代是使用的堆内存,如果创建对象太多容易造成内存溢出OOM(OutOfMemory)。

jdk8以后:
jdk8以后便取消了永久代的说法,而是用元空间代替,所存内容没有变化,只是存储的地址有所改变,元空间使用的是主机内存,而不是堆内存,元空间的大小限制受主机内存限制,这样有效的避免了创建大量对象时发生内存溢出的情况。

六、Minor GC和Full GC

Minor GC和Full GC,的区别

Minor GC即新生代GC:发生在新生代的垃圾收集动作,因为Java有朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
Major GC / Full GC:发生在老年代,经常会伴随至少一次Minor GC。Major GC的速度一般会比Minor GC慢倍以上。

Minor GC发生条件:

当新对象生成,并且在Eden申请空间失败时;

Full GC发生条件:

老年代空间不足
永久带空间不足(jdk8以前)
System.gc()被显示调用
Minor GC晋升到老年代的平均大小大于老年代的剩余空间
使用RMI来进行RPC或管理的JDK应用,每小时执行1次Full GC

七、Java OOM问题如何排查

什么是OOM

OOM为out of memory的简称,来源于java.lang.OutOfMemoryError,指程序需要的内存空间大于系统分配的内存空间,OOM后果就是程序crash;可以通俗理解:程序申请内存过大,虚拟机无法满足,然后自杀了。

导致OOM问题的原因

1)分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
2)应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。

内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。

内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

最常见的OOM情况有以下三种:

  • java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
  • java.lang.OutOfMemoryError: PermGen space 或 java.lang.OutOfMemoryError:MetaSpace ------>java方法区,(java8 元空间)溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
  • java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。

排查手段

一般手段是:先通过内存映像工具对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏还是内存溢出。

如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。这样就能够找到泄漏的对象是通过怎么样的路径与GC Roots相关联的导致垃圾回收机制无法将其回收。掌握了泄漏对象的类信息和GC Roots引用链的信息,就可以比较准确地定位泄漏代码的位置。

内存溢出,那么就是内存中的对象确实必须存活着,那么此时就需要通过虚拟机的堆参数( -Xmx和-Xms)来适当调大参数;从代码上检查是否存在某些对象存活时间过长、持有时间过长的情况,尝试减少运行时内存的消耗。

七、常见的垃圾收集器(jdk8及以前)

一张图即可清除看到不同垃圾收集器之间的关系,连线表示可以配合使用。

Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。

Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本。

ParNew收集器(复制算法)
新生代收集器,Serial收集器的多线程版本,在多核CPU情况时表现更好。

Parallel Scavenge收集器(复制算法)
并行收集器,追求高吞吐量,高效利用CPU。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=2来指定线程数。

Parallel Old收集器(复制算法) Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。
CMS(Concurrent Mark Sweep)收集器(标记-清理算法) 高并发、低停顿,追求最短GC回收停顿时间(Stop The World),cpu占用比较高,响应时间快,停顿时间短,多核cpu追求高响应时间的选择,但是因为使用标记清理算法,容易产生内存碎片。
G1收集器
G1是一款面向服务端应用的垃圾收集器,支持并行与并发、分代收集、空间整合和可预测停顿的能力,即可适用于年轻代又可适用于老年代。

图片来源:https://cloud.tencent.com/developer/article/1336613

八、垃圾收集器参数总结

UseSerialGC:虚拟机运行在Client模式下的默认值,打开此开关后,使用Serial+Serial Old的收集器组合进行内存回收
UseParNewGC:打开此开关后,使用ParNew+Serial Old的收集器组合进行内存回收
UseConcMarkSweepGC:打开此开关后,使用ParNew+CMS+Serial Old的收集器组合进行内存回收。Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用
UseParallelGC:虚拟机运行在Server模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收
UseParallelOldGC:打开此开关后,使用Parallel Scavenge + Parallel Old的收集器组合进行内存回收
SurvivorRatio:新生代中Eden区域与Survivor区域的容量比值,默认值为8,代表Eden:Survivor=8:1
PretenureSizeThreshold:直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
MaxTenuringThreshold:晋升到老年代的对象年龄,每个对象在坚持过一次Minor GC之后,年龄就增加1,当超过这个参数时就进入老年代
UseAdaptiveSizePolicy:动态调整Java堆中各个区域的大小以及进入老年代的年龄
HandlePromotionFailure:是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象都存活的极端情况
ParallelGCThreads:设置并行GC时进行内存回收的线程数
GCTimeRatio:GC时间占总时间的比率,默认值为99,即允许1%的GC时间。仅在使用Parallel Scavenge收集器时生效
MaxGCPauseMillis:设置GC的最大停顿时间,仅在使用Parallel Scavenge收集器时生效
CMSInitingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%,仅在使用CMS收集器时生效
UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用CMS收集器时生效
CMSFullGCsBeforeCompaction:设置CMS收集器在进行若干次垃圾收集后再启动一次内存碎片整理。仅在使用CMS收集器时生效

JDK、JRE、JVM区别与联系

JVM

jvm(Java Virtual Machine),java虚拟机,java运行时的环境,是一种用于计算机设备的规范,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
java虚拟机在执行字节码时,把字节码解释成具体平台的机器指令。
jvm实现了平台无关性,jvm屏蔽了具体操作平台的具体信息,使得java程序只需要生成在jvm上运行的字节码,就可以在不同平台上不加修改的运行,实现了一次编译多处运行。

JRE

JRE(Java Runtime Environment,Java运行环境)
包含jvm和java核心类库。是Java运行环境,

JDK

JDK(Java Development Kit) 是java开发工具包,包括了JRE。

GC

怎么定义垃圾

引用计数法

在对象中引入一个计数器,每当有一个地方引用它就加一,引用失效就减一。当引用计数器为0时就意味着是垃圾。

但是主流的虚拟机都没用选用引用计数法,因为必须要配合大量的额外处理才能够保证正确地工作,譬如简单的引用计数法就无法解决对象之间的循环引用。

可达性分析法

将GC Root作为起始节点,从这些节点开始,根据引用关系向下搜索,搜索时走过的路径称为“引用链”,若某个对象到GC Root之间没用任何引用链连接,或者用图论的话来说,从GC Root到这个对象不可达时,则证明该对象是垃圾
GCRoot:以下地方直接或间接引用的对象

  1. 栈:正在使用不能删除
  2. 本地方法栈
  3. 方法区:全局变量,需要保留。

GC算法

1.标记—清理法

在对象后面标记,再扫描一遍就将其删除掉。
缺点:产生内存碎片

2.标记—整理法

在清除之后,后面对象往前面顶
缺点:前移代价大

3.复制算法

将内存分为1区和2区,在1区创建对象,之后标记1区是否需要删除,在1区快满的时候将不需要删除,紧凑的复制到2区。
缺点:需要两倍的内存。

实际GC算法

将java堆分为young区和old区。

young区的垃圾回收器是ParNew垃圾回收器。按照8:1:1将young区分为eden区,以及两个servivor区,来交替工作,提高利用率。新创建的对象一般被分配到eden区,当该区满了之后就会触发一次young GC,采用复制算法,将eden区不需要删除的对象复制到servivor区。

old区,在yongGC时年龄加一,直到满了6岁,就会复制到old区。同时存一些大对象。在old GC会同时伴随young GC,所以又称full GC,full GC 会引起stop the world,整个程序暂停,采用标记-清理。标记-整理。

在正式Minor GC之前会检查young区中的对象,比old区剩余空间大还是小。假如Minor GC后survivor区放不下剩余对象,这些对象就要进入old区。

  1. old区剩余空间大于young区对象大小,就直接GC。就算survivor区不够放,old区还是够放的
  2. 小于。查看是否启用了“老年代空间分配担保规则”
  1. 如果老年代剩余空间大小大于历次Minor GC之后剩余对象大小,那就允许Minor GC.因为从概率上讲,以前放得下,现在也放得下。那就会产生两种情况
  2. 如果小于,进行full GC.

GC失败原因

  1. full GC 仍然放不下,只能OOM.
  2. 未开启老年代分配担保规则,且full GC后仍然放不下,只能OOM.
  3. 开启了老年代分配担保规则,但是担保不通过,只能OOM.

虚拟机堆和栈的区别

1. 功能和作用:

栈是用来执行程序的,堆是用来存放对象的

栈:栈可以看作方法的运行模型,所有方法的调用都是通过栈帧实现的,jvm会为每个线程分配一个栈,jvm只对栈进行压栈和出栈操作。当一个线程进入一个java方法函数时,就会在当前栈中压入一个栈帧保存当前线程的状态,当退出函数时,会讲该

2. 性能与存储要求:

3. 内存的分配与回收:

类加载过程

1. 加载

在加载阶段,java虚拟机需要完成以下三件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个java.lang.class对象,作为方法区这个类的各种数据访问入口。

加载阶段结束后,java虚拟机外部字节流就按照虚拟机规定的格式存储在方法区中了,之后会在java堆内存中实例化一个java.lang.class对象1,这个对象作为程序访问方法区中的类型数据的外部接口。

2.验证

确保class文件的字节流中包含的信息符合《java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

  1. 文件格式验证
    验证字节流是否符合class文件格式规范,并能被当前版本虚拟机处理。
  2. 元数据验证
    对字节码描述的信息进行语义分析,以保证其描述的信息符合《java语言规范》的要求。
  3. 字节码验证
    通过数据流分析和控制流分析,确定程序语义是否合法、符合逻辑。
  4. 符合引用验证
    对类自身以外的各种类型进行匹配性校验,检查该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。

3.准备

正式为类中定义的变量(静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。从概念上讲,这些变量所使用的内存都应该放在方法区,但是在JDK及之前都又永久代实现,实现是完全符合逻辑概念的。在JDK8及以后,就随着class对象一起存放在堆内存中,这时候“类变量在方法区”就完全是逻辑概念的表述了。

4.解析

java虚拟机讲常量池中的符号引用替换为直接引用,符合引用在class文件中以constant_class_info等类型的常量出现

符合引用:以一组符合来描述所引用的目标,符合可以是任何形式的字面量,只要能无歧义的定位到目标即可,符合引用与虚拟机
直接引用:可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的语柄。直接引用和虚拟机实现的内存布局相关的,同一个符号在不同虚拟机中的直接引用可能不同。若有了直接引用,那引用的目标必定在虚拟机中的内存存在。

5.初始化

是类加载最后一个步骤,之前的几个阶段除了加载阶段用户应用程序可以通过自定义类加载器的方式外,其余都是由java虚拟机来主导控制。直到初始化阶段,java虚拟机才真正开始执行类中的编写的java代码,将主导权交给应用程序。
准备阶段,变量已经有初始值,在初始化阶段,会根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源。

jvm内存

jvm虚拟机拿到了自己能支配的内存之后,将内存进行割分,分为五个部分。
在这里插入图片描述
本地方法栈:c++这些native方法。
程序计数器:指向程序当前运行的位置
方法区:存储元数据信息,jdk7及之前叫永久代,jdk8之后改名为元数据空间。存储静态方法或变量,类加载器,等全局数据信息。
堆区:存对象
栈区:存函数临时变量,存储对象引用,地址

全局共享:堆区,方法区
线程私有:栈区,本地方法栈,程序计数器。

线程私有

程序计数器

较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令,每个线程都有独立的程序计数器,用来在线程切换后能恢复到正确的执行位置,各线程之间的程序计数器互不影响,是线程私有的。此内存区域是唯一一个在jvm规范中没用规定任何OutOfMemoryError情况的区域。

是线程私有的,描述的是java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧用于存储局部变量表,方法出口等信息。每个方法从调用到完成都便随着栈帧的入栈和出栈。每当一个方法执行完成时,该栈桢就会弹出元素作为这个方法的返回值,并清除这个栈帧。

局部变量:存放了各种基本类型数据,对象引用。

在jvm规范中,对这个区域规定了两种异常

  1. StackOverfloxError:线程请求的栈深度大于虚拟机允许的深度
  2. OutOfMemoryError:若虚拟机可以动态扩展,在扩展时无法申请到足够的内存

本地方法栈

本地方法栈和虚拟机栈所发挥的作用是很相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈为虚拟机使用到的native方法服务

全局共享

存储着所有实例对象,由垃圾收集器自动回收,占用内存最大,但也会消耗完所有空间,内存空间可以固定大小,也可以通过参数动态调整

方法区

存储已经被虚拟机加载的类信息,常量,静态变量。运行时常量池也是方法区一部分,用于存储运行时生成的各种字面常量和符号引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值