前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文搞懂JVM内存结构,程序员必须掌握的知识

一文搞懂JVM内存结构,程序员必须掌握的知识

作者头像
黎明大大
发布2020-09-08 16:23:06
4140
发布2020-09-08 16:23:06
举报
文章被收录于专栏:java相关资料java相关资料
jvm内存结构组成

Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

简要言之,jmm是jvm的一种规范,定义了jvm的内存模型。它屏蔽了各种硬件和操作系统的访问差异,不像c那样直接访问硬件内存,相对安全很多,它的主要目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。可以保证并发编程场景中的原子性、可见性和有序性。

从下图可以看出,java内存模型分为五大数据区域,这些区域都有各自的用途以及他们的创建时间和销毁时间

其中方法区和堆是所有线程共享的,栈,本地方法栈和程序虚拟机则为线程私有的。

程序计数器(PC寄存器)
代码语言:javascript
复制
程序计数器是一块很小的区域,它是线程独占区域,可以认为它是线程行号的指示器。
栈空间
代码语言:javascript
复制
同程序计数器一样是线程独占区域
每个方法被执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法返回地址等信息,每一个方法被调用的过程就是对应一个栈帧从虚拟机入栈到出栈过程(栈是先进后出)

看如下图,可以知道代码执行是先从main方法执行,入栈开始,然后调用add方法,add方法在进入入栈,执行完毕之后add出栈,最后才main方法出栈,所以着对应着一个栈是先进后出的结果(一个方法对应一个栈帧)

且对于栈帧的详细解释参考 Java虚拟机运行时栈帧结构

代码语言:javascript
复制
栈帧:是存储数据结构,以及部分过程结果
栈帧的位置: 内存 -> 运行时数据区 -> 某个线程对应的虚拟机栈 -> here[在这里]
栈帧大小确认时间: 是在编译的时候就确认好了,不会在运行的时候受到数据变化的影响

需要注意的是,局部变量表所需要的空间大小是在编译的过程中就已经分配好了,当进入一个方法的时候,在栈中所需要的局部变量表的空间是完成确认的,在方法运行期间是不会受到数据的影响

代码语言:javascript
复制
Java虚拟机栈可能出现两种类型的异常:
1.线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
2.虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。
本地方法栈
代码语言:javascript
复制
本地方法栈与虚拟机栈的作用其实相差不大,最大一个区别就是虚拟机栈执行的是java方法(也就是字节码),而本地方法栈则为虚拟机使用的native方法,native方法主要是调用的是c或者c++

可以用过unsafe类查看这些方法

堆空间
代码语言:javascript
复制
对于大多数应用来说,堆是java虚拟机中最大的一块区域,因为他存储的的对象线程是共享的,所以在多线程情况下也需要进行同步机制。
且主要存储就是对象本身以及数组
方法区
代码语言:javascript
复制
方法区跟堆一样,是所有线程共享的区域,在jdk8中叫元数据区域
用于存储每个类的信息(类的名称,方法信息,字段信息)、静态变量、常量、以及编译器编译之后的代码等
(注:在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。)

注意:在老版jdk,方法区也被称为永久代【因为没有强制要求方法区必须实现垃圾回收,HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,Hotspot虚拟机便将运行时常量池从永久代移除了。】

jdk1.7开始逐步去永久代。从String.interns()方法可以看出来 String.interns() native方法:作用是如果字符串常量池已经包含一个等于这个String对象的字符串,则返回代表池中的这个字符串的String对象,在jdk1.6及以前常量池分配在永久代中。可通过 -XX:PermSize和-XX:MaxPermSize限制方法区大小。

代码语言:javascript
复制
static String base = "test";

/**
 * 方式一:需要全局变量,不需要设定堆空间大小
 * @param args
 */
public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        String str = base + base;
        base = str;
        list.add(str.intern());
    }
}

/**
 * 方式二:需要设定堆空间大小,不需要全局变量
 */
public static void main(String[] args) {
    //用list保持着引用 防止full gc回收常量池
    List<String> list = new ArrayList<String>();
    int i = 0;
    while (true) {
        list.add(String.valueOf(i++).intern());
    }
}

//以上其中一个即可测试
//如果在jdk1.6环境下运行 同时限制方法区大小 将报OOM后面跟着PermGen space说明方法区OOM,即常量池在永久代
//如果是jdk1.7或1.8环境下运行 同时限制堆的大小  将报heap space 即常量池在堆中

idea设置相关内存大小设置

这边暂时不用全局的方式,设置main方法的vm参数即可 做相关的设置,比如说设定堆大小(-Xmx5m -Xms5m -XX:-UseGCOverheadLimit)

代码语言:javascript
复制
这边如果不设置UseGCOverheadLimit将报java.lang.OutOfMemoryError: GC overhead limit exceeded,
这个错是因为GC占用了多余98%(默认值)的CPU时间却只回收了少于2%(默认值)的堆空间。目的是为了让应用终止,给开发者机会去诊断问题。一般是应用程序在有限的内存上创建了大量的临时对象或者弱引用对象,从而导致该异常。虽然加大内存可以暂时解决这个问题,但是还是强烈建议去优化代码,后者更加有效,也可通过UseGCOverheadLimit避免[不推荐,这里是因为测试用,并不能解决根本问题]

jdk8真正开始废弃永久代,转而使用元空间(Metaspace)

java虚拟机对方法区比较宽松,除了跟堆一样可以不存在连续的内存空间,定义空间和可扩展空间,还可以选择不实现垃圾收集。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • jvm内存结构组成
  • 程序计数器(PC寄存器)
  • 栈空间
  • 本地方法栈
  • 堆空间
  • 方法区
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档