JVM的位置:jvm通过操作系统来操作使用系统硬件。
JVM体系结构
在实际运行中 程序计数器和虚拟机栈、本地方法栈是线程独有的,方法区和Heap是线程共享的。如下图:
java 虚拟机虚拟机自动管理内存,不容易出现内存泄漏和溢出问题,但是一旦出现问题排查起来比较艰难
运行时内存区域
Java虚拟机在执行java程序的时候把内存分为若干不同的数据区域,每个区域有各自的用途:
方法区、虚拟机栈、本地方法栈、堆、程序计数器。方法区又称为非堆,是JVM规范,1.7之前方法区的实现是永久代,1.8永久代被元空间替代。
程序计数器
程序计数器是一块较小的内存空间,它可以看做是当前线程执行的字节码的行号指示器。线程私有的内存.如果执行的是一个java方法,这个计数器记录的是正在执行字节码指令的地址,如果正在执行的是一个native方法,这个计数器值为空.
为什么需要记录 当前线程执行的字节码的行号 ?因为 cpu 执行代码最小单位是线程。线程只有得到cpu的时间片 才能执行,否则会处于挂起状态,当再次需要执行时必须知道应该从哪里开始执行。
java虚拟机栈
与程序计数器一样java虚拟机也是线程私有的,它的生命周期和线程相同。虚拟机描述的是java方法执行的内存模型, 每个方法执行时创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口(返回地址)等信息。每个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
有人把内存分为堆内存和栈内存 这种方法比较粗糙。栈就是指的虚拟机栈或者说是虚拟机栈中局部变量表部分。
java 虚拟机规范中规定了两种异常状况:
如果线程请求的栈深度大于虚拟机所允许的深度 抛出 stackOverflowError异常
如果虚拟机栈是可以动态扩展的,如果扩展时无法申请到足够的内存会抛出 outofmemeryError异常
本地方法栈
和虚拟机栈作用相似 区别是 虚拟机栈为执行java方法服务,本地方法栈为虚拟机使用到的native方法服务。虚拟机规范中对使用方式 和数据结构没有强制规定,因此可以自由实现它。也会抛出和 虚拟机栈同样的异常。
java堆
对于大多数应用来说java堆 是java虚拟机所管理内存中最大的一块。被所有线程共享 所有对象实例在这里分配内存。但是随着JIT编译器的发展和逃逸分析技术逐渐成熟 ,所有的对象都分配在堆上变得不那么绝对了。(在开启栈内分配后虚拟机在执行方法时如果判断局部变量不会逃逸则会优先直接在栈内为对象分配内存)
java堆 是垃圾收集齐管理的主要区域 因此 很多时候被称作 GC堆
垃圾回收基本都采用分代收集算法 所以java堆可以细分为 新生代和老年代
再细致一些可以分为 eden空间 from survivor 空间和 to survivor 空间等。
可以处于物理上不连续的内存中,逻辑上连续即可 。当前主流虚拟机都是按照可扩展来实现的。 -Xmx 和 -Xms控制。
如果在堆中没有足够内存完成实例分配,并且堆也无法扩展时 抛出 outofmemoryError异常
方法区
和java堆一样是线程共享的内存区域。用于存储已经给虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
java虚拟机方法区描述为堆的一个部分,但是又有一个名字叫非堆
hotspot 虚拟机上很多人把方法区称为永久代(1.7以前)。本质上两者不等价 hs 上GC可以管理方法区内存,对于其他虚拟就不存在永久代的概念
java.1.7中已经将 原本在永久代的字符串常量池移出。
java虚拟机规范对方法区限制非常宽松可以选择 固定代销或可扩展内存 还可以选择不识闲垃圾回收
垃圾收集行为较少但也不是进入方法区就永久存在 当方法区无法满足内存分配需求时抛出 outOfmemoryError异常
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据比较难回收,关闭 JVM 才会释放此区域所占用的内存。如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。
Jdk1.6及之前: 有永久代, 常量池1.6在方法区
Jdk1.7: 有永久代,但已经逐步“去永久代”,常量池1.7在堆
Jdk1.8及之后: 无永久代,常量池1.8在元空间
运行时常量池
是方法区的一部分 ,Class信息除了 版本、字段、方法、接口描述信息外还有一项 信息是常量池:编译器生成的各种字面量和符号引用
并不一定只有编译期产生常量。运行期间也可能将新的常量放入池中
当无法申请到内存时 抛出 outofmemoryError
堆外内存
不是数据区内存的一部分,也不是java虚拟机规范中定义的内存区域 但是也会频繁使用 导致outofmemoryError异常
java1.4引入了NIO,可以使用native函数直接分配堆外内存,然后操作这块内存 ,这样显著提高性能
本机直接内存分配不会受到java堆大小的限制,但是受到本机总内存及处理器寻址空间的限制
创建一个对象的过程
当多个线程同时请求分配内存时,就可能出现多个线程抢同一块内存的情况。为了解决这个问题 会预先为每个线程分配一块缓冲区,线程申请内存时直接从缓冲区申请。