首页 > 应用开发 > Java > JVM基础——运行时数据区
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储,称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。 如果执行Native 方法,这个计数器则为空(Undefined).
线程私有,不会存在内存溢出
java虚拟机也是线程私有的,它的生命周期和线程相同。 虚拟机栈描述的是Java方法执行的内存模型 : 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
是一组变量值存储空间, 主要用于存储方法参数和定义在方法体内的局部变量,包括编译器可知的各种 Java 虚拟机 基本数据类型(boolean、byte、char、short、int、float、long、double)、 对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此相关的位置)和 returnAddress 类型(指向了一条字节码指令的地址,已被异常表取代)
由于局部变量表是建立在线程的栈上,是线程的私有数据,因此 不存在数据安全问题。
局部变量表所需要的容量大小是编译期确定下来的,并保存在方法的 Code 属性的
maximum local variables 数据项中。在方法运行期间是不会改变局部变量表的大小的。
也常称为操作栈, 主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。它是一个后入先出栈(LIFO)。当一个方法刚刚开始执行时,其操作数栈是空的,随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程。
每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的 Code 属性的
max_stack 数据项中。
在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池。在 Java 源文件被编译到字节码文件中时,所有的变量和方法引用都作为 符号引用(Symbolic Reference)保存在 Class 文件的常量池中。
Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态连接(Dynamic Linking)。
这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接。
当一个方法开始执行时,可能有两种方式退出该方法(正常完成的出口,异常完成的出口)
栈帧中还允许携带与 Java 虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息,但这些信息取决于具体的虚拟机实现。
关于虚拟机栈和本地方法栈,在<<Java 虚拟机规范>> 重描述了两种异常。
1、如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常(栈帧过多导致栈内存溢出(递归调用)或者 栈帧过大导致栈内存溢出)
2、如果虚拟机机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMermoryError异常。(需要创建新的线程 HotSpot 虚拟机选择不支持扩展)
与虚拟机所发挥的作用非常相似,他们之间的区别不过是虚拟机栈执行java服务,本地方法栈执行虚拟机使用的Native服务.
Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块,Java堆是被所有 线程共享的一块内存区域,此内存区域的唯一目的就 ,是存放对象实例,几乎所有的对象实例都在这里分配内存。
线程共享,需要考虑线程安全问题,有垃圾回收机制。
新生带(年轻代):新对象和没达到一定年龄的对象都在新生代
老年代(养老区):被长时间使用的对象,老年代的内存空间应该要比年轻代更大
元空间(JDK1.8 之前叫永久代):像一些方法中的操作临时对象等,JDK1.8 之前是占用 JVM 内存,JDK1.8 之后直接使用物理内存
方法区( Method Area ) 与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap ( 非堆),目的应该是与Java堆区分开来。
类型的信息
类型常量池
存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类型、字段、方法的符号引用。
字段信息
方法信息
运行时常量池( Runtime Constant Pool)是 方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是 常量池( Constant Pool Table ) ,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
Java 中基本类型的包装类的大部分都实现了常量池技术,这些类是 Byte、Short、Integer、Long、Character、Boolean,另外 Float 和 Double 类型的包装类则没有实现。另外 Byte、Short、Integer、Long、Character 这5种整型的包装类也只是在对应值在-128到127之间时才可使用对象池。
直接内存(Direct Memory ) 并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError 异常出现,所以我们放到这里一起讲解。( 分配回收成本高,但读写性能高,不受jvm内存回收管理)
在JDK 1.4中新加入了NIO ( Newlnput/Output) 类 ,引入了一种基于通道(Channel ) 与缓冲区(Buffer ) 的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中。DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。( 开辟一块系统内存和java堆内存都可以访问的内存,提高性能)
使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
ByteBuffffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffffer 对象,一旦ByteBuffffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
不用多说,相信大家都有一个共识:无论什么行业,最牛逼的人肯定是站在金字塔端的人。所以,想做一个牛逼的程序员,那么就要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。
现在竞争这么激烈,只有通过不断学习,提高自己,才能保持竞争力。
对于一些不知道学习什么,没有一个系统路线的程序员,这里给大家提供一些学习资料
需要的小伙伴,可以一键三连, 点击这里获取免费领取方式!
内容涵盖:Java基础、JVM、高并发、多线程、分布式、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、数据库、云计算等
需要的小伙伴,可以一键三连, 点击这里获取免费领取方式!
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70000430/viewspace-2788290/ ,如需转载,请注明出处,否则将追究法律责任。