Java深入浅出之JVM内存模型

一、虚拟机的发展

HotSpot VM(SUN)  以前使用范围最广的Java虚拟机

JRockit VM(BEA)   号称”世界上最快的Java虚拟机”

J9 VM(IBM)

Dalvik VM( Google )

HotSpot VM(ORACLE) 目前使用范围最广的Java虚拟机

二、JVM的整体介绍

三、运行时数据区

这个是抽象概念,内部实现依赖寄存器、高速缓存、主内存(具体要分析 JVM 源码 C++ 语言实现,没必要看)
计算机的运行 = 指令 + 数据,指令用于执行方法的,数据用于存放数据和对象的。
虚拟机栈 ---- 执行 java 方法、本地方法栈 --- 执行本地方法、程序计数器 --- 程序执行的计数器
Java 中的数据:变量、常量、对象、数组相关。

四、程序计数器

较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响(面试可能问到为什么需要)
如果线程正在执行的是一个 Java 方法,则指明当前线程执行的代字节码行数
如果正在执行的是 Natvie 方法,这个计数器值则为空( Undefined
此内存区域是唯一一个不会出现 OutOfMemoryError 情况的区域。

五、栈

栈(Stack):数据结构  入口和出口只有一个 入栈 出栈 

特点 先进后出(FIL0)

异常:

线程请求的栈深度大于虚拟机所允许的深度:StackOverflowError

JVM动态扩展时无法申请到足够的内存时:OutOfMemoryError

六、虚拟机栈

每个线程私有的,线程在运行时,在执行每个方法的时候都会打包成一个栈帧,存储了局部变量表,操作数栈,动态链接,方法出口等信息,然后放入
栈。每个时刻正在执行的当前方法就是虚拟机栈顶的栈桢。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。
栈的大小缺省为 1M,可用参数 –Xss 调整大小,例如-Xss256k
在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的 Code 属性之中,因此一个栈帧需要分
配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
局部变量表:顾名思义就是局部变量的表,用于存放我们的局部变量的。首先它是一个 32 位的长度,主要存放我们的 Java 的八大基础数据类型,一般 32
位就可以存放下,如果是 64 位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的 Object 对象,我们只需要存放它的一个引用
地址即可。(基本数据类型、对象引用、returnAddress 类型)
操作数据栈:存放我们方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的 java 数据类
型,所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的,操作数栈运行方法是会一直运行入栈/出栈的操作
动态连接:Java 语言特性多态(需要类加载、运行时才能确定具体的方法,后续有详细的讲解)
返回地址:
正常返回(调用程序计数器中的地址作为返回)
三步曲:
恢复上层方法的局部变量表和操作数栈、
把返回值(如果有的话)压入调用者栈帧的操作数栈中、
调整 PC 计数器的值以指向方法调用指令后面的一条指令、
异常的话(通过异常处理器表<非栈帧中的>来确定)

七、本地方法栈

各虚拟机自由实现,本地方法栈 native 方法调用 JNI 到了底层的 C/C++(c/c++ 可以触发汇编语言,然后驱动硬件 )

八、线程共享区域

方法区(永久代(JDK1.7和以前)、元空间(JDK1.8)) 类信息 常量 静态变量 即时编译期编译后的代码

Java堆(-Xms;-Xmx;-Xmn) 堆是需要重点关注的一块区域,因为涉及到内存的分配(new关键字,反射等)与回收(回收算法,收集器等) 几乎所有的对象都是在堆中分配.

九、JVM各版本内存区域的变化

运行时常量池      Class 文件中的常量池(编译器生成的各种字面量和符号引用)会在类加载后被放入这个区域。   

JDK1.6     运行时常量池在方法区中

JDK1.7     运行时常量池在堆中

JDK1.8     去永久代:使用元空间(空间大小只受制于机器的内存)替代永久代     

永久代参数    

-XX:PermSize;-XX:MaxPermSize =100M   超过100M OOM()      

元空间参数    

-XX:MetaspaceSize; -XX:MaxMetaspaceSize

为什么会这样呢?

永久代来存储类信息、常量、静态变量等数据不是个好主意, 很容易遇到内存溢出的问题。 对永久代进行调优是很困难的,同时将元空间与堆的垃圾回收进行了隔离,避免永久代引发的Full GC和OOM等问题;

十、直接内存

不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;

如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;

这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常;

避免了在Java 堆和Native 堆中来回复制数据,能够提高效率

十一、站在线程角度看JVM

十二、深入分析堆和栈

功能

以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;(stackoverflow,OOM)

而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;

线程独享还是共享

栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。

堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

空间大小

栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题。

十三、JVM中对象的分配

十四、对象的内存布局

十五、对象的访问方式

句柄是一种特殊的智能指针 。句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。

十六、堆内存分配策略

堆进一步划分 新生代(PSYoungGen) Eden空间   From Survivor空间 To Survivor空间 老年代(ParOldGen)

堆中参数配置:

新生代大小: -Xmn20m 表示新生代大小20m(初始和最大)

-XX:SurvivorRatio=8 表示Eden和Survivor的比值, 缺省为8 表示 Eden:From:To= 8:1:1 2 Eden:From:To=  2:1:1

对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保

后续会持续更新JVM算法!!!

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值