JVM的基础知识点Java的内存模型

Stella981
• 阅读 574

阅读文本大概需要3分钟。

Java虚拟机是Java工程师必学的进阶功课,这段时间开始死磕JVM。今天梳理一下JVM的基础知识点Java的内存模型!

JVM的基础知识点Java的内存模型

程序计数器

是什么:程序计数器是很小的一块内存空间,它是当前线程所执行的字节码的行号指示器。

有什么用:解释器通过这个计数器来选取下一条需要执行的字节码指令。
存储什么内容:如果线程执行的是Java方法,存储的是正在执行的虚拟机字节码指令的地址;如果是native方法,计数器值为空(undefined)。

为什么是线程私有的:多线程是线程轮流切换并分配处理器执行时间片的方式来实现的,在任何确定的时刻,一个处理器(对于多核处理器来说就是一个内核)都只会执行一条线程,所以,为了线程在切换后能恢复到正确的执行位置,每个线程应该独立拥有一个程序计数器。

会出现什么异常情况:唯一一个无内存溢出异常的区域。

Java虚拟机栈

是什么:虚拟机栈是Java方法的内存模型,每一个Java方法从调用到执行完成就对应着一个栈帧在虚拟机栈中的入栈和出栈。

存储什么内容:每个方法的执行就会创建一个栈帧,这个栈帧会存储这个Java方法的局部变量表,操作数栈,动态链接,方法出口等信息。

为什么是线程私有的:每个线程所执行的方法可能是不一样的。

会出现什么异常情况:如果线程请求的栈深度>虚拟机允许的深度,抛出栈溢出异常;如果扩展时无法申请到足够的内存,抛出内存溢出异常。

本地方法栈

是什么:本地方法栈的作用和虚拟机栈非常像是,只不过本地方法栈是native方法的内存模型,每一个native方法从调用到执行完成就对应着一个栈帧在本地方法栈中的入栈和出栈。

存储什么内容:同虚拟机栈。

为什么是线程私有的:同虚拟机栈。

会出现什么异常情况:同虚拟机栈。

Java堆

是什么:Java堆是Java虚拟机管理的内存中最大的一块,Java堆是在虚拟机启动的时候创建的。

存储什么内容:存放对象实例,几乎所有的对象实例都在这个内存区域分配内存。

为什么是线程共享的:所有的线程都可以访问不同的对象。其实从内存分配的角度来看,线程共享的Java堆可能其实是多个线程私有的分配缓冲区,不同的线程将各自的对象实例放在看似共享的Java堆的各自的缓冲区上,这样划分可以更好的回收内存,也可以更好点分配内存。

会出现什么异常情况:Java堆可以处于物理上不连续的内存空间上,但逻辑上一定是连续的,在堆中没有内存可以完成对象实例的分配,且无法再扩展时,会抛出内存溢出异常。

方法区

是什么:和堆一样,是各个线程共享的内存区域。很多人把方法区称为永久代,但是本质上这两个不等价,Java虚拟机将GC分代收集扩展至方法区,使用永久代来实现方法区,这样GC收集器就能像管理Java堆一样管理方法区而不需要再写一套GC收集来管理方法区。当然在方法区里也可以设置不进行GC收集。

存储什么内容:已被虚拟机加载的类信息,类常量,类的静态变量,即时编译器编译后的代码等。运行时常量池也是方法区的一部分。

为什么是线程共享的:各个线程都可以访问虚拟机加载的类。

会出现什么异常情况:内存溢出异常。

直接内存

是什么:直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机定义的内存区域,但也经常被使用。JDK1.4加入了NIO类,一种基于通道与缓冲区的新I/O方式,NIO可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为直接内存的引用来操作直接内存,这样可以避免在Java堆和native堆来回复制数据,从而提高了性能。

会出现什么异常情况:受机器总内存的影响,会出现内存溢出异常。

Java虚拟机中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常;

  • 如果在虚拟机中无法申请到足够多的内存空间,将抛出OutOfMemoryError异常。

我们都知道Java虚拟机各个内存区域(除了程序计数器)都有发生内存溢出的可能,但到底什么样的操作或程序才会导致内存溢出或栈溢出的异常呢?我们分不同的内存区域来解释这个问题。

0x01、对于Java堆内存区域

Java堆中只会产生OutOfMemoryError异常。

先搞清楚Java堆内存放的是什么,还不清楚的可以回顾下这篇文章《死磕JVM-Java内存模型》,从这篇文章里我们知道Java堆内存存放的是对象实例,所以原理上只要我们不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清楚这些对象,也就是说当Eden区满的时候,GC被触发时,让GC误以为内存中的对象还存活着,那么在对象数量达到最大堆容量限制的时候就会产生内存溢出的异常。如下代码就会产生内存溢出的异常:

public class 堆溢出{     static class OOMError{}     public static void main(String[] args){          List<OOMError> list =new ArrayList<OOMError>();          while(true){               list.add(newOOMError());          }     }

运行结果:

Exceptionin thread "main" java.lang.OutOfMemoryError:Java heap space     at java.util.Arrays.copyOf(Arrays.java:3210)     at java.util.Arrays.copyOf(Arrays.java:3181)     at java.util.ArrayList.grow(ArrayList.java:261)     at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)     at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)     at java.util.ArrayList.add(ArrayList.java:458)     at com.intelligentler.jvm.堆溢出.main(堆溢出.java:13)

“Java heap space”提示着产生OutOfMemoryError异常的Java虚拟机的内存区域,也就是Java堆内存。

如何解决发生在Java堆内存的OutOfMemoryError异常呢?

首先我们要分清楚产生OutOfMemoryError异常的原因是内存泄露还是内存溢出,如果内存中的对象确实都必须存活着而不像上面那样不断地创建对象实例却不使用该对象,则是内存溢出,而像上面代码中的情况则是内存泄露。

如果是内存泄露,我们可以通过一些内存查看工具来查看泄露对象到GC Roots的引用链,找到泄露对象是通过怎样的路径与GC Roots相关联并导致GC无法自动回收这些泄露对象,掌握了这些信息,我们就能比较准确地定位出泄露代码的位置。

如果不是内存泄露,也就是说内存中的对象确实都还必须存活,那么应该检查虚拟机的堆参数,看看是否还可以将机器物理内存调大,同时在代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况。

0x02、对于虚拟机栈和本地方法栈

在这一部分内存区域,可能产生OutOfMemoryError异常和StackOverflowError异常。

如果定义大量的本地变量,增大此方法帧中本地变量表的长度或者设置-Xss参数减少栈内存容量,这两种操作都会抛出StackOverflowError异常,如下面的代码:

public class 栈溢出{     privateint stackLength =1;     publicvoid addStackLength(){          stackLength++;          addStackLength();     }     public static void main(String[] args)throws Throwable{          栈溢出 oom =new 栈溢出();          try{               oom.addStackLength();          }catch(Throwable e){               System.out.println("stack length:"+ oom.stackLength);               throw e;          }     }}

运行结果:

stack length:18388Exceptionin thread "main" java.lang.StackOverflowError     at com.intelligentler.jvm.栈溢出.addStackLength(栈溢出.java:9)     at com.intelligentler.jvm.栈溢出.addStackLength(栈溢出.java:9)     at com.intelligentler.jvm.栈溢出.addStackLength(栈溢出.java:9)

所以,如果在单线程的情况下,无论是栈帧太大还是虚拟机栈容量太小,当内存无法再分配的时候,虚拟机抛出的是StackOverflowError异常。

如果在多线程下,不断地建立线程可能会产生OutOfMemoryError异常。

0x03、对于方法区

方法区中只会产生OutOfMemoryError异常。

由于运行时常量池是方法区的一部分,我们可以通过String.intern()方法来构建一个运行时常量池的OutOfMemoryError异常。

String.intern()是一个Native方法,它的作用是:如果字符串常量池中已经包含了一个等于该String对象的字符串,则返回这个String对象,否则,将此String对象包含的字符串添加到常量池中,并返回这个字符串的String对象的引用。如下面代码:

public class 方法区溢出{     public static void main(String[] args){          List<String> list =newArrayList<String>();          int i =0;          while(true){               list.add(String.valueOf(i++).intern());          }     }}

运行结果:

Exceptionin thread "main" java.lang.OutOfMemoryError:PermGen space    at java.lang.String.intern(NativeMethod)

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,也就是说运行时常量池属于方法区(也就是虚拟机永久代)中的一部分。

另外,方法区是存放Class的相关信息的,运行时如果有大量的类来填满方法区,就会产生OutOfMemoryError异常。

往期精彩

01 Sentinel如何进行流量监控

02 Nacos源码编译

03 基于Apache Curator框架的ZooKeeper使用详解

04 spring boot项目整合xxl-job

05 互联网支付系统整体架构详解

关注我

每天进步一点点

JVM的基础知识点Java的内存模型

喜欢!在看☟

本文分享自微信公众号 - JAVA乐园(happyhuangjinjin88)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这