了解JVM

一、JVM

1、基本概念

  JVM全称为Java Virtual Machine,也叫Java虚拟机,JVM诞生之初,最主要的目的是为了跨平台,因为在那个年代里,计算机行业还处于一个不稳定状态,各种操作系统和各种语言层出不穷,在一台电脑上编译另外一台电脑上执行就成为了重要需求,所有JVM诞生了,JVM处于应用程序和操作系统之间,相当于起到一个翻译的作用,它可以根据操作系统的不同,把文件翻译成不同的机器指令。但是现在编程大概率已经不需要跨平台了,所以跨平台就成了"伪需求",但是JVM却依然存在,主要是因为现在的JVM还有另外一些重要作用,像类加载、执行引擎(解释执行字节码文件)、动态内存管理(申请和释放内存,也叫垃圾回收)。

2、内存划分

  JVM会在执行Java程序的过程中把它管理的内存划分为若干个不同的数据区域。这些数据区域各有各的用处,各有各的创建与销毁时间,有的区域随着JVM进程的启动而存在,有的区域则依赖用户线程的启动和结束而创建与销毁。一般来说,JVM所管理的内存将会包含以下几个运行时数据区域:

  • 线程私有区域:程序计数器、Java虚拟机栈、本地方法栈
  • 线程共享区域:Java堆、方法区、运行时常量池

在这里插入图片描述

栈(线程私有)

  在JDK1.8之后本地方法栈和虚拟机栈合并在一起了。虚拟机栈是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,栈帧会随着方法的调用而创建,随着方法的结束而销毁,这就是为什么局部变量为什么不能影响全局的原因;本地方法栈与虚拟机栈的作用完全一样,他俩的区别无非是本地方法栈为虚拟机使用的Native方法服务,而虚拟机栈为JVM执行的Java方法服务。

  在栈帧中有一块空间叫做局部变量表,它里面存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表大小
在这里插入图片描述

程序计数器

  程序计数器也叫PC寄存器,Java中的PC寄存器是对物理机中PC寄存器的一种抽象模拟,它是一块内存很小的空间,也是执行速度最快的一块空间,它里面储存的是下一条指令的地址,也是将要执行的指令,如果执行的native方法的话,则计数器为空;每个线程都有自己的程序计数器,相当于就是和线程的生命周期是一致的,这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError 情况的区域。

程序计数器储存指令地址有什么作用?为什么要设计成线程私有的?

 答:首先我们知道线程是切换执行的,一个线程可能并没有执行完,就会切换到下一个线程,如果等线程再次切换回来,不可能会从头开始执行,而是需要一个记录,然后从记录这个二位置开始执行代码,这就是程序计数器的作用;而设置成线程私有是因为每一个线程都是一个单独的执行流,为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不相互干扰。

  Java堆(Java Heap)是JVM所管理的最大内存区域。Java堆是所有线程共享的一块区域,在JVM启动时创建。此内存区域存放的都是对象实例。JVM规范中说到:“所有的对象实例以及数组都要在堆上分配”,其中所有的对象还包括每个类生成的Class对象。堆可以处于物理上不连续的内存中;堆是垃圾回收器管理的主要区域,从GC的角度来看,还可以把堆分为新生代和老年代。如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM。

方法区

  方法区存储了被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;方法区本质上只是JVM中的一个概念,不同的JVM对方法区可以有不同的实现,在 HotSpotVM中就使用Java堆的永久代来实现方法区,这样做的好处是在垃圾回收时可以对永久代一起回收,提高了效率,但在之后的版本中永久代又被元空间所取代,也就是说方法区已经名存实亡了,JVM加载的信息直接储存在一块本地内存中,这样做的好处是储存的大小就不会受分配的空间大小限制了;且JVM还规定了,当方法区无法满足内存分配需求时,将抛出OOM异常。

  在方法区中还有一块地方叫做运行时常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

二、异常

1、Java堆溢出

有以下两种情况会导致堆溢出:

  1. 内存泄漏:主要是指申请出的内存空间用完没有及时释放,而导致内存的浪费,这种情况现在已经很少会出现了,在开始时使用的是引用计数的方式去回收空间,这样会有循环引用的情况出现,而导致不能回收这块内存,但是现在使用了可达性分析,这种情况就会变少。
  2. 内存溢出:由于我们不断的去开辟空间,导致堆空间已经满了,无法在分配空间时,就会内存溢出;此时要根据JVM堆参数与物理内存相比较检查是否还应该把JVM堆内存调大;或者检查对象的生命周期是否过长。
public class Test {
    static class Memory{

    }
    public static void main(String[] args) {
        List<Memory> list = new ArrayList<>();
        while (true){
            list.add(new Memory());
        }
    }
}

通过上述例子我们看出,内存泄漏是由于没有释放内存导致的,那么实际情况中我们不释放内存到底可不可以呢?
 答:实际情况是我们在上面谈到的堆和方法区之类的,都属于是JVM的一部分,而JVM在运行程序时其实就是一个进程,如果程序执行完成,进程就随之释放,那么那些没有释放的内存也就跟着释放了。但这有个条件,就是程序要执行完,释放JVM进程,我们平时使用的都是客户端,所以可以结束进程,但是还有服务器呢,服务器的工作一般都是7*24小时工作,进程永远不会停止,这时发生内存泄漏就很严重了。

2、栈溢出

  栈溢出在我们平常写代码时算是比较常见的一种异常了,像递归条件错误,导致循环开辟栈帧,就会导致栈溢出,如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM异常。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值