java 内存接口_【java内存】内存结构

开局3张图,结论全靠吹,全文所有内容都围绕这3个图展开。

1.java语言编译解释过程

我们编写的java语言,经过java源码编译器后会编译成jvm字节码,也就是.class文件。

然后再通过类加载机制,也就是classloader及其子类来完成jvm的类加载。

进一步jvm字节码被加载入内存,进入jvm虚拟机,被解释器解释执行。

2.jvm体系结构

分为四个部分:类加载器(classloader)、执行引擎(execution engine)、本地接口(native interface)、运行数据区(runtime data area)

2.1类加载器

负责加载 .class文件,class文件在文件开头有特定的文件标示,并且ClassLoader负责class文件的加载等,至于它是否可以运行,则由Execution Engine决定。

① 定位和导入二进制class文件

② 验证导入类的正确性

③ 为类分配初始化内存

④ 帮助解析符号引用.

2.2本地接口

本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候C/C++横行的时候,要想立足,必须有调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体作法是本地方法栈中登记native方法,在Execution Engine执行时加载native libraies。

目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见。

因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等。

2.3执行引擎

执行在包装在装载类中的方法中的指令,也就是方法。

2.4运行数据区

计算机内存会专门拿出来一块内存的区域,用来存放jvm的对象、变量。具体分为线程共享区,即方法区、栈,以及线程私有区,即程序计数器、虚拟机栈、本地方法栈。

3.运行数据区(即:虚拟机内存或者JVM内存)

运行时区域分为线程共享区域和线程私有区域,就是共享区域中的方法、对象、变量都能够被所有的线程访问,线程私有区域只能被线程自己所访问。

3.1程序计数器

每个线程都有一个程序计数器,其实本质就是一个指针,指向方法区中的程序字节码,也就是下一个要执行的指令地址,由执行引擎来控制读取下一个指令,所占用内存空间很小。

其实程序计数器可以被看作为,当前线程所执行的字节码的行号指示器,在虚拟机的概念模型里面,字节码计时器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。

3.2虚拟机栈

虚拟机栈其实是java方法执行时的动态模型,在每个方法执行的时候都会创建一个栈帧,用来储存局部变量表、操作数栈,动态的链接还有方法出口等信息,局部变量表存放的是编译期的基本数据类型,引用类型,returnaddress等,局部变量表的内存在编译期就完成分配了,进入到方法之后,这个方法在帧中分配的内存就固定了,在方法运行的期间是不会改变局部变量表的大小,如果帧堆满了整个栈,就会出现栈溢出现象,出现StackOverflowError(栈溢出)异常,栈也可以申请更大的内存,如果申请不到,会抛出OutOfMemoryError异常。

每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

栈的生命期是跟随线程的生命期,线程创建时创建,线程结束栈内存也就释放,是线程私有的。

局部变量表:存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。

3.3本地方法区栈

Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies

本地方法栈与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务

3.4方法区

用来存储虚拟机加载所需要的静态变量、常量、类信息以及运行常量池,其中类信息又包括类的版本、字段、方法、接口、构造函数等描述信息。

默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。

在方法区中其实还有一块运行常量池,当class文件中的常量池在类加载之后就被放入运行常量池,运行常量池还可以通过intern将常量放入进去,因此其具有动态性,一旦方法区中空间不足时候会抛出OutofMemoryerror异常。

3.5堆

堆的存在更重要的是用来存放对象的实例和数组的,但是有了逃逸分析技术,也可以将对象放在栈上面。

在使用堆存储时,能够动态的分配内存空间,而不需要知道存储的数据的生命周期。这种动态分配内存大小的代价是用堆进行存储分配和清理时比

堆是垃圾回收的主要区域,关于垃圾回收的判断方法分为,引用技术法和根搜素算法,对于垃圾的处理分为标记消除法、复制算法、标记整理法、分代处理法。垃圾回收主要还是分代处理,分为新生代和老年代,新生代主要用复制算法处理,老年代用标记整理法或者标记消除法。

堆也可以不是物理上连续的区域,只需要在逻辑上是连续的就可以类,在堆上分配内存的方法有碰撞指针和空闲列表的方式,当堆空间不足时候也会出现内存不足的异常。

3.6.直接内存 Direct Memor

直接内存并不是JVM管理的内存,可以这样理解,直接内存,就是JVM以外的机器内存,比如,你有4G的内存,JVM占用了1G,则其余的3G就是直接内存

JDK中有一种基于通道(Channel)和缓冲区(Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用

由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。

4.对象的创建过程

在JVM在堆中为对象分配内存阶段,通常有以下两种分配方式,虚拟机选择哪种分配方式是由JAVA堆是否规整决定的,而JAVA堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

1)指针碰撞:要求堆中内存绝对规整,所有用过的内存都放一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅只是将该指针向空闲空间那边挪动一段与对象大小相等的距离。

2)空闲列表:针对的是堆中内存不规整的情况,虚拟机维护着一个列表,记录哪些内存块是可用的,在分配时从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

在为对象分配内存时,还需要考虑的一点就是线程安全性问题。可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。针对这种问题,有以下两种解决方案。

1)对分配内存空间的动作进行同步处理,保证更新操作的原子性(采用CAS + 失败重试机制保障原原子性),但效率较低。

2)使用本地线程分配缓冲(TLAB),哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并需要分配新的TLAB时,才需要同步锁定(可通过-XX:+/-UseTLAB参数来设定虚拟机启用TLAB)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值