jvm基础

基本概念

JVM是可以运行Java代码的假想计算机,包括一个栈、一个堆、一个寄存器、一个垃圾回收、一个存储方法域和一套字节码指令集。JVM是运行在操作系统上的,与硬件没有直接的交互。我们编写好我们的程序,它就会经历:JAVA源文件 --> 编译器进行编译 --> 生产对应的.class文件(字节码文件),而JAVA虚拟机(JVM)里的解释器,则会把该字节码文件编译成特定机器上的机器码。
不同平台的解释器是不一样的,但是实现的虚拟机是一样的,这就是为什么Java能够跨平台的原因。当一个程序开始运行,Java虚拟机就会开始实例化,多个程序就会实例化出多个虚拟机。虚拟机伴随程序诞生和消亡(程序退出或关闭),它们之间的数据不能共享。

线程

这里的线程指程序执行过程中的一个线程实体,JVM允许一个程序并发执行多个线程。Hotspot JVM 中的Java线程和原生操作系统线程有直接的映射关系,当线程本地存储、缓冲器分配、同步对象、栈、程序计数器等准备好以后,就会创建一个原生操作系统线程。Java线程结束后,就会回收对应的原生线程。操作系统负责调度所有原生线程,把它们分配到可用的CPU上。当原生线程初始化完毕,就会调用Java线程的run方法。当线程结束时,会释放原生线程和Java线程所有资源。

JVM内存区域

分为线程私有区域、线程共享区域和直接内存。
(1)线程私有区域的生命周期和Java线程相同,依赖它的启动/结束 而进行创建/销毁(在Hotspot JVM中Java线程和本地原生线程直接映射,所以线程私有区域也和本地原生线程的创建和释放对应)
(2)线程共享区域随JVM的启动和关闭而进行创建和销毁
(3)直接内存不是JVM运行时的数据区组成部分,但是也经常使用到它。在JDK1.4中引入的NIO提供了基于channel与buffer的IO方式。它能用本地方法库直接分配堆外内存,然后使用directbytebuffer对象作为这块内存的引用进行操作,可以避免在Java堆和本地堆中来回复制数据,在一些场景显著提高了性能。
在这里插入图片描述

程序计数器(线程私有)

是当前线程所执行的字节码的行号指示器,每条线程都有一个独立的程序计数器,所以程序计数器所在的内存是线程私有的一块较小的内存空间。
正在执行Java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令)。如果是Native方法,则为空。
这个内存区域是唯一一个在虚拟机中没有规定任何outofmemoryerror情况的区域。

虚拟机栈(线程私有)

是描述Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(stack frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
每一个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。
栈帧是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(dynamic linking)、方法返回值和异常分派(dispatch exception)。栈帧随着方法调用而创建,随着方法结束而销毁–无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常),都算作方法结束。在这里插入图片描述

本地方法区(线程私有)

本地方法区和Java stack作用类似,区别是虚拟机栈为执行Java方法服务,而本地方法栈则为native方法服务,如果一个VM实现使用C-linkage模型来支持native调用,那么该栈将是一个C栈,但hotspot vm直接就把本地方法栈和虚拟机栈合二为一。

堆(Heap-线程共享)-运行时数据区

是被线程共享的一块内存区域,创建的对象和数组都保存在Java堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代VM采用分代收集算法,因此Java堆从gc的角度还可以细分为:新生代(eden区、from survivor区和to survivor区)和老年代。

方法区/永久代(线程共享)

即我们常说的永久代(permanent generation),用于存储被jvm加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot VM把gc分代收集扩展至方法区,即使用Java堆的永久代来实现方法区,这样Hotspot的垃圾收集器就可以像管理Java堆一样管理这块内存,不用单独开发专门的内存管理器(永久代的内存回收的主要目标是针对常量池的回收和类型的卸载,收集一般很小)。
运行时常量池(runtime constant pool)是方法区的一部分,class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息就是常量池(constant pool table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。Java虚拟机对class文件的每一部分(包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

下面我们对Java堆进行详细介绍

Java堆从GC的角度还可以细分为:新生代(Eden区、From Survivor区和To Survivor区)和老年代。
在这里插入图片描述
如图所示,新生代占了堆的1/3的空间,而它又分为Eden区、ServivorFrom、ServivorTo 三个区。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。

新生代

Eden区

Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。

ServivorFrom区

上一次GC的幸存者,作为这一次GC的被扫描者。

ServivorTo 区

保留了一次MinorGC过程中的幸存者。

MinorGC 的过程 (复制–清空–互换)
MinorGC采用复制算法
1:Eden区、ServivorFrom复制到ServivorTo ,年龄+1
首先,把eden和ServivorFrom区域中存活的对象复制到ServivorTo 区域(如果有对象的年龄达到了老年的标准,则赋值到老年代区),同时这些对象的年龄+1(如果ServicorTo不够位置就放到老年区);
2:清空eden、ServivorFrom中的对象;
3:ServicorTo 和 ServicorFrom 互换,原ServicorTo成为下一次GC的ServicorFrom区。

老年代

主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前都会先进行一次MinorGC,让新生代的对象晋升为老年代,老年代空间不够,就触发MajorGC。当新创建的较大对象放不进新生代区,要直接放入老年代区时,老年代区无法找到足够大的连续空间分配给它时,就会提前触发一次MajorGC进行垃圾回收,为这个较大对象腾出空间。
MajorGC采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记处理方便下次直接分配。当老年代也满了装不下,就会抛出 oom(out of memory)异常。

永久代

指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域,它和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以也导致永久代的区域会随着加载的Class的增多而胀满,抛出OOM异常。

Java8和元数据

在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。所以在默认情况下, 元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入Java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值