转自:https://blog.csdn.net/tjiyu/article/details/53915869

下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些,然后分别介绍它们的特点,并指出给出一些HotSpot虚拟机实现的不同点和调整参数。

1、Java内存区域概述

1-2、Java内存区域与JVM运行时数据区

如上图, Java虚拟机规范定义了字节码执行期间使用的各种运行时数据区,即JVM在执行Java程序的过程中,会把它管理的内存划分为若干个不同的数据区域,包括:

程序计数器、java虚拟机栈、本地方法栈、java堆、方法区、运行时常量池;

从线程共享角度来说,可以分为两类:

1、所有线程共享的数据区

方法区、运行时常量池、java堆;

这些数据区域是在Java虚拟机启动时创建的,只有当Java虚拟机退出时才会被销毁;

2、线程间隔离的数据区

程序计数器、java虚拟机栈、本地方法栈、

这些数据区域是每个线程的"私有"数据区,每个线程都有自己的,不与其他线程共享;

每个线程的数据区在创建线程时创建,并在线程退出时被销毁;

3、另外,还一种特殊的数据区

直接内存--使用Native函数库直接分配的堆外内存;

即Java内存区域 = JVM运行时数据区 +直接内存。

2、Java各内存区域说明

上面图片展示的是JVM规范定义的运行时数据概念模型,实际上JVM的实现可能有所差别,下面在介绍各内存数据区时会给出一些HotSpot虚拟机实现的不同点和调整参数。

2-1、程序计数器

程序计数器(Program Counter Register),简称PC计数器;

1、生存特点

每个线程都需要一个独立的PC计数器,生命周期与所属线程相同,各线程的计数器互不影响;

2、作用

JVM字节码解释器通过改变这个计数器的值来选取线程的下一条执行指令;

3、存储内容

JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的;

在任意时刻,一个线程只会执行一个方法的代码(称为该线程的当前方法(Current Method));

(A)、如果这个方法是Java方法,那PC计数器就保存JVM正在执行的字节码指令的地址;

(B)、如果该方法是native的,那PC计数器的值是空(undefined);

4、内存分配特点

PC计数器占用较小的内存空间;

容量至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值;

5、异常情况

唯一一个JVM规范中没有规定会抛出OutOfMemoryError情况的区域;

2-2、Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stack,JVM Stack),指常说的栈内存(Stack);

和Java堆指的堆内存(Heap),都是需要重点关注的内存区域;

1、生存特点

每个线程都有一个私有的,生命周期与所属线程相同;

2、作用

描述的是Java方法执行的内存模型,与传统语言中(如C/C++)的栈类似;

在方法调用和返回中也扮演了很重要的角色;

3、存储内容

用于保存方法的栈帧(Stack Frame);

每个方法从调用到执行结束,对应其栈帧在JVM栈上的入栈到出栈的过程;

栈帧:

每个方法执行时都会创建一个栈帧,随着方法调用而创建(入栈),随着方法结束而销毁(出栈);

栈帧是方法运行时的基础结构;

栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息;

(A)、局部变量表

局部变量表(Local Variables Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。

这些都是在编译期可知的数据,所以一个方法调用时,在JVM栈中分配给该方法的局部变量空间是完全确定的,运行中不改变;

一个方法分配局部变量表的最大容量由Class文件中该方法的Code属性的max_locals数据项确定;

(B)、操作数栈

操作数栈(Operand Stack)简称操作栈,它是一个后进先出(Last-In-First-Out,LIFO)栈;

在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容(任意类型的值),也就是入栈/出栈操作;

在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果;

一个方法的操作数栈长度由Class文件中该方法的Code属性的max_stacks数据项确定;

(C)、动态链接

每一个栈帧内部都包含一个指向运行时常量池的引用,来支持当前方法的执行过程中实现动态链接 (Dynamic Linking);

在 Class 文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的;

动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用(除了在类加载阶段解析的一部分符号);

4、内存分配特点

因为除了栈帧的出栈和入栈之外,JVM栈从来不被直接操作,所以栈帧可以在堆中分配;

JVM栈所使用的内存不需要保证是连续的;

JVM规范允许JVM栈被实现成固定大小的或者是根据计算动态扩展和收缩的:

(A)、固定大小

如果JVM栈是固定大小的,则当创建新线程的栈时,可以独立地选择每个JVM栈的大小;

(B)、动态扩展或收缩

在动态扩展或收缩JVM栈的情况下,JVM实现应该提供调节JVM栈最大和最小内存空间的手段;

两种情况下,JVM实现都应当提供调节JVM栈初始内存空间大小的手段;

HotSpot VM通过"-Xss"参数设置JVM栈内存空间大小;

5、异常情况

JVM规范中对该区域,规定了两种可能的异常状况:

(A)、StackOverflowError

如果线程请求分配的栈深度超过JVM栈允许的最大深度时,JVM将会抛出一个StackOverflowError异常;

(B)、 OutOfMemoryError

如果JVM栈可以动态扩展,当然扩展的动作目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那JVM将会抛出一个OutOfMemoryError异常;

该区域与方法执行的JVM字节码指令密切相关,这里篇幅有限,以后有时间会分析方法的调用与执行过程,再来详细介绍该区域。

2-3、本地方法栈

本地方法栈(Native Method Stack)与 Java虚拟机栈类似;

1、与Java虚拟机栈的区别

Java虚拟机栈为JVM执行Java方法(也就是字节码)服务;

本地方法栈则为Native方法(指使用Java以外的其他语言编写的方法)服务;

2、HotSpot VM实现方式

JVM规范中没有规定本地方法栈中方法使用的语言、方式和数据结构,JVM可以自由实现;

HotSpot VM直接把本地方法栈和Java虚拟机栈合并为一个;

2-4、Java堆

Java堆(Java Heap)指常说的堆内存(Heap);

1、生存特点

所有线程共享;

生命周期与JVM相同;

2、作用

为"new"创建的实例对象提供存储空间;

里面存储的这些对象实例都是通过垃圾收集器(Garbage Collector)进行自动管理,所以Java堆也称"GC堆"(Garbage Collected Heap);

对GC堆以及GC的参数设置调整,就是JVM调优的主要内容;

3、存储内容

用于存放几乎所有对象实例;

(随JIT编译技术和逃逸分析技术发展,少量对象实例可能在栈上分配,详见后面介绍JIT编译的文章);

4、内存分配特点

(A)、Java堆划分

为更好回收内存,或更快分配内存,需要对Java堆进行划分:

(I)、从垃圾收集器的角度来看

JVM规范没有规定JVM如何实现垃圾收集器;

由于很多JVM采用分代收集算法,所以Java堆还可以细分为:新生代、老年代和永久代;

(II)、从内存分配角度来看

为解决分配内存线程不安全问题,需要同步处理;

Java堆可能划分出每个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),减少线程同步;

HotSpot VM通过"-XX:+/-UseTLAB"指定是否使用TLAB;

(B)、分配调整

和JVM栈一样,Java堆所使用的物理内存不需要保证是连续的,逻辑连续即可;

JVM规范允许Java堆被实现成固定大小的或者是根据计算动态扩展和收缩的:

两种情况下,JVM实现都应当提供调节JJava堆初始内存空间大小的手段;

在动态扩展或收缩的情况下,还应该提供调节最大和最小内存空间的手段;

(C)、HotSpot VM相关调整

目前主流的JVM都把Java堆实现成动态扩展的,如HotSpot VM:

(1)、初始空间大小

通过"-Xms"或"-XX:InitialHeapSize"参数指定Java堆初始空间大小;

默认为1/64的物理内存空间;

(2)、最大空间大小

通过"-Xmx"或"-XX:MaxHeapSize"参数指定ava堆内存分配池的最大空间大小;

默认为1/4的物理内存空间;

Parallel垃圾收集器默认的最大堆大小是当小于等于192MB物理内存时,为物理内存的一半,否则为物理内存的四分之一;

(3)、各年代内存的占用空间与可用空间的比例

通过"-XX:MinHeapFreeRatio"和"-XX:MaxHeapFreeRatio"参数设置堆中各年代内存的占用空间与可用空间的比例保持在特定范围内;

默认:

"-XX:MinHeapFreeRatio=40":即一个年代(新生代或老年代)内存空余小于40%时,JVM会从未分配的堆内存中分配给该年代,以保持该年代40%的空余内存,直到分配完"-Xmx"指定的堆内存最大限制;

"-XX:MaxHeapFreeRatio=70":即一个年代(新生代或老年代)内存空余大于70%时,JVM会缩减该年代内存,以保持该年代70%的空余内存,直到缩减到"-Xms"指定的堆内存最小限制;

这两个参数不适用于Parallel垃圾收集器(通过“-XX:YoungGenerationSizeIncrement”、“-XX:TenuredGenerationSizeIncrement ”能及“-XX:AdaptiveSizeDecrementScaleFactor”调节);

(4)、年轻代与老年代的大小比例

通过"-XX:NewRatio":控制年轻代与老年代的大小比例;

默认设置"-XX:NewRatio=2"表新生代和老年代之间的比例为1:2;

换句话说,eden和survivor空间组合的年轻代大小将是总堆大小的三分之一;

(5)、年轻代空间大小

通过"-Xmn"参数指定年轻代(nursery)的堆的初始和最大大小;

或通过"-XX:NewSize"和"-XX:MaxNewSize"限制年轻代的最小大小和最大大小;

(6)、定永久代空间大小

通过"-XX:MaxPermSize(JDK7)"或"-XX:MaxMetaspaceSize(JDK8)"参数指定永久代的最大内存大小;

通过"-XX:PermSize(JDK7)"或"-XX:MetaspaceSize(JDK8)"参数指定永久代的内存阈值--超过将触发垃圾回收;

注:JDK8中永久代已被删除,类元数据存储空间在本地内存中分配;

详情请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62

(D)、调整策略

关于这些参数的调整需要垃圾收集的一些知识(以后文章会介绍),先来简单了解:

当使用某种并行垃圾收集器时,应该指定期望的具体行为而不是指定堆的大小;

让垃圾收集器自动地、动态的调整堆的大小来满足期望的行为;

调整的一般规则:

除非你的应用程序无法接受长时间的暂停,否则你可以将堆调的尽可能大一些;

除非你发现问题的原因在于老年代的垃圾收集或应用程序暂停次数过多,否则你应该将堆的较大部分分给年轻代;

5、异常情况

如果实际所需的堆超过了垃圾收集器能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError异常;

该部分的内存如何分配、垃圾如何收集,上面这些参数如何调整,将在以后的文章详细说明。

2-5、方法区

方法区(Method Area)是堆的逻辑组成部分,但有一个别名"Non-Heap"(非堆)用以区分;

1、生存特点

所有线程共享;

生命周期与JVM相同;

2、作用

为类加载器加载Class文件并解析后的类结构信息提供存储空间;

以及提供JVM运行时常量存储的空间;

3、存储内容

用于存储JVM加载的每一个类的结构信息,主要包括:

(A)、运行时常量池(Runtime Constant Pool)、字段和方法数据;

(B)、构造函数、普通方法的字节码内容以及JIT编译后的代码;

(C)、还包括一些在类、实例、接口初始化时用到的特殊方法;

4、内存分配特点

(A)、分配调整

和Java堆一样,所使用的物理内存不需要保证是连续的;

或以实现成固定大小的或者是根据计算动态扩展和收缩的;

(B)、方法区的实现与垃圾回收

JVM规范规定:

虽然方法区是堆的逻辑组成部分,但不限定实现方法区的内存位置;

甚至简单的虚拟机实现可以选择在这个区域不实现垃圾收集;

因为垃圾收集主要针对常量池和类型卸载,效果不佳;

但方法区实现垃圾回收是必要的,否则容易引起内存溢出问题;

(C)、HotSpot VM相关调整

(I)、在JDK7中

使用永久代(Permanent Generation)实现方法区,这样就可以不用专门实现方法区的内存管理,但这容易引起内存溢出问题;

有规划放弃永久代而改用Native Memory来实现方法区;

不再在Java堆的永久代中生成中分配字符串常量池,而是在Java堆其他的主要部分(年轻代和老年代)中分配;

更多请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/enhancements-7.html

(II)、在JDK8中

永久代已被删除,类元数据(Class Metadata)存储空间在本地内存中分配,并用显式管理元数据的空间:

从OS请求空间,然后分成块;

类加载器从它的块中分配元数据的空间(一个块被绑定到一个特定的类加载器);

当为类加载器卸载类时,它的块被回收再使用或返回到操作系统;

元数据使用由mmap分配的空间,而不是由malloc分配的空间;

通过"-XX:MaxMetaspaceSize" (JDK8)参数指定类元数据区的最大内存大小;

通过"-XX:MetaspaceSize" (JDK8)参数指定类元数据区的内存阈值--超过将触发垃圾回收;

详情请参考:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref62

5、异常情况

如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常;

2-6、运行常量池

运行常量池(Runtime Constant Pool)是方法区的一部分;

1、存储内容

是每一个类或接口的常量池(Constant_Pool)的运行时表示形式;

包括了若干种不同的常量:

(A)、从编译期可知的字面量和符号引用,也即Class文件结构中的常量池;

(B)、必须运行期解析后才能获得的方法或字段的直接引用;

(C)、还包括运行时可能创建的新常量(如JDK1.6中的String类intern()方法)

2-7、直接内存

直接内存(Direct Memory)不是JVM运行时数据区,也不是JVM规范中定义的内存区域;

1、特点

是使用Native函数库直接分配的堆外内存;

被频繁使用,且容易出现OutOfMemoryError异常;

2、作用

因为避免了在Java堆中来回复制数据,能在一些场景中显著提高性能;

3、实现方式

JDK1.4中新加入NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式;

它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java椎中的DirectByteBuffer对象作为这块内存的引用进行操作;

4、HotSpot VM相关调整

可以通过"-XX:MaxDirectMemorySize"参数指定直接内存最大空间;

不会受到Java堆大小的限制,即"-Xmx"参数限制的空间不包括直接内存;

这容易导致各个内存区域总和大于物理内存限制,出现OutOfMemoryError异常;

到这里,我们大体了解Java各内存区域是什么,有些什么特点了,但方法执行的JVM字节码指令如何在Java虚拟栈中运作的,以及Java堆内存如何分配、垃圾如何收集,如何进行JVM调优,将在以后的文章详细说明。

后面我们将分别去了解:方法的调用与执行、JIT编译--在运行时把Class文件字节码编译成本地机器码的过程、以及JVM垃圾收集相关内容……

Java内存管理:Java内存区域 JVM运行时数据区的更多相关文章

  1. 你必须了解的java内存管理机制(一)-运行时数据区

    前言 本打算花一篇文章来聊聊JVM内存管理机制,结果发现越扯越多,于是分了四遍文章(文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8),本文为其中第一篇.from 你必须了解的java内存 ...

  2. 介绍下Java内存区域(运行时数据区)

    介绍下Java内存区域(运行时数据区) Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域.JDK 1.8 和之前的版本略有不同. 下图是 JDK 1.8 对JV ...

  3. JVM内存区域(运行时数据区)划分

    前言: 我们每天都在编写Java代码,编译,执行.很多人已经知道Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文 ...

  4. JVM运行时数据区及对象在内存中初始化的过程

    JVM运行时数据区 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter R ...

  5. Java中的字符串常量池和JVM运行时数据区的相关概念

    什么是字符串常量池 JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量 ...

  6. JVM运行时数据区与JVM堆内存模型小结

    前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 ...

  7. Jvm运行时数据区 —— Java虚拟机结构小记

    关于jvm虚拟机的文章网上都讲烂了.尤其是jvm运行时数据区的内容. 抱着眼见为实的想法,自己翻了翻JVM规范,花了点时间稍微梳理了一下. 以下是阅读Java虚拟机规范(Java SE 8版)的第二章 ...

  8. Jvm运行时数据区

    一:运行时数据区 Java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域.这些区域有着各自的用途,一级创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户 ...

  9. JVM总结(一):概述--JVM运行时数据区

    大三下,趁着寒假重温一遍JVM,准备在一个系列来总价一下学习JVM的整个过程.争取在接下来的一个星期内更新完这一个系列,然后回家过年. JVM运行时数据区 线程私有的数据区 程序计数器 虚拟机栈 本地 ...

随机推荐

  1. themeleaf跳转锚链接

    <a class="lianjie3" th:href="@{/}+'#requires'"></a>

  2. HDU-1247 Hat’s Words (暴力)【Trie树】

    <题目链接> 题目大意: 给你一些单词,要求输出将该单词完全分成前.后两个单词之后,若这两个单词都在单词库中出现,则输出该单词. 解题分析: 将每个单词的每一位能够拆分的位置全部暴力枚举一 ...

  3. Oracle no TOP, how to get top from order

    On ROWNUM and Limiting Results Our technologist explains how ROWNUM works and how to make it work fo ...

  4. HTML5:在移动端禁用长按选中文本功能

    很多时候,我们在写的手机页面需要用户进行长按然后响应一个事件.但是在微信中用户的长按操作被默认为谈出来一个复制的选项.那么这个时候如何去禁止这个东西呢? 其实很简单,方法看下面: 只需要在你需要禁止的 ...

  5. c++中static变量有什么用

    主要有两点用途. 1.让一个变量长期有效,而不管其是在什么地方被申明.比如: int fun1() { static int s_value = 0; .... } 那么fun1不管在什么地方被调用, ...

  6. java多态的向上转型与向下转型(与编译时类型与运行时类型有关)

    1.编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定. 当编译时类型和运行时类型不一致时,就会出现所谓的多态. 因为子类是一个特殊的父类,因此java允许把一个子类对象直接 ...

  7. acm--博弈入门2(P/N分析)--(HDU 1847 HDU 2188 HDU 3863)

    P/N理论 分析博弈时可以用P/N分析法 具体如下: P点:即必败点,某玩家位于此点,只要对方无失误,则必败: N点:即必胜点,某玩家位于此点,只要自己无失误,则必胜. 必败态:一定输 必胜态:一定赢 ...

  8. NIOH

    目录 NIOH中的双刀与阴阳术的应用 作战准备篇 2周目毕业装备: 加点: 双刀: 核心技能: 还行的技能: 被动技能: 忍术: 阴阳术: 必学: 选学: 守护灵: 隐世茶室 & 铁匠铺 出发 ...

  9. Django——User-Profile

    Profile作用:User内置的字段不够完善,导致创建的用户信息单一,Profile就是为了对User进行扩展,即丰富用户信息 在models中创建Profile类,添加字段user与User形成O ...

  10. sklearn神经网络分类

    sklearn神经网络分类 神经网络学习能力强大,在数据量足够,隐藏层足够多的情况下,理论上可以拟合出任何方程. 理论部分 sklearn提供的神经网络算法有三个: neural_network.Be ...