JVM基础知识之虚拟机栈、本地方法栈

目录

虚拟机栈

虚拟机栈的内存结构

虚拟机栈的异常处理

对象引用关系导致对象无法被GC回收

应用程序过度使用finalizer

栈深度大于虚拟机所允许的深度

本地方法栈(Native Method Stack)

本地方法

本地方法栈的内存结构

本地方法栈的异常管理

总结

虚拟机栈

       java虚拟机栈是一个内存区域,这个区域用于存储局部变量部分结果。每个线程都有一个私有的java虚拟机栈,它的存在随着线程的创建而创建,也就是说,java虚拟机栈会随着线程的销毁而销毁。

       初学者可能对于“部分结果”的表述不太明白,这里我举个例子。我们知道,java的方法是要有返回值的,“部分结果”存储的就是方法的返回值。a+b就被存储在了虚拟机栈中。

public int add(int a, int b) {
    return a + b;
}

虚拟机栈的内存结构

        java程序中实现特定功能的是方法,也就是函数,在方法被执行的时候,JVM会同步创建一个栈帧(Stack Frame),栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等一些信息。当一个方法调用另一个方法时,虚拟机也会为这个新的方法创建新的栈帧。

虚拟机栈的异常处理

       作为虚拟机用于存储方法调用的数据结构,在方法执行过程中,有可能会发生异常,首先有必要了解一下哪些原因会造成异常的发生。常见的原因有以下几种:

  • 再分配对象、吞吐量增加时,程序无意保存了对象引用,导致对象无法被GC回收
  • 应用程序过度使用finalizer
  • 线程请求的栈深度大于虚拟机所允许的深度

其实理论上来讲,异常可能存在各种各样,这篇文章就先从这三个主要的方面来挖掘一下解决办法

对象引用关系导致对象无法被GC回收

       对于这种异常,可以通过手动解除对象引用关系,或者使用弱引用类型来标记不强制保留对象的引用关系,这样可以让垃圾回收器正确识别并回收无用的对象

应用程序过度使用finalizer

       由于虚拟机中的垃圾回收器在回收对象时,如果对象恰好有创建的finalize()方法,当某个类重写了finalize()方法,但是没有正确地实现该方法,导致该方法无法正常执行,从而导致虚拟机栈出现异常。

       对于这种异常其实没有捷径,只有我们尽量少使用finalize()方法,或者要确保finalize()方法的正确使用

栈深度大于虚拟机所允许的深度

       我们知道,当方法调用时,方法的参数,局部变量和返回值等信息会存储在栈帧中,每进行一次方法调用这一过程会产生一个新的栈帧,当栈帧数量超过java虚拟机所允许的深度时,就会抛出StackOverflowError异常,常用的有两种解决办法:

  • 检查代码中是否存在死循环或者递归调用,这些操作容易导致栈深度异常。
  • 人为增加虚拟机栈的大小,通过-Xss参数来设置大小,但也要注意,不应该设置的过大,过大又会导致虚拟机栈内存溢出异常

本地方法栈(Native Method Stack)

本地方法

       与虚拟机栈有类似之处,本地方法栈也是线程私有的,不同的是,虚拟机栈是作为管理java方法的调用的存在,本地方法栈用于管理native方法的调用,比如Thread.start0()。对于本地方法,我们可以理解为非java语言实现的方法。来看一个经典的例子:

public class NativeDemo {
   static {
      System.loadLibrary("NativeDemo");
   }

   private native int add(int a, int b);

   public static void main(String[] args) {
      NativeDemo demo = new NativeDemo();
      int sum = demo.add(10, 20);
      System.out.println("Sum: " + sum);
   }
}

       java的本地方法指的是用C或者C++编写的方法,通过java的本地接口(java Native Interface)在java虚拟机外部实现,并被java代码指令调用。编写本地方法的目的主要是用于实现与操作系统或硬件相关的功能,比较深入到计算机底层,有时当我们要调用本地库中的函数时,也会用到本地方法。这也是为什么有时候要使用本地方法的原因,当我们想要实现和系统底层交互,或者想要追求程序的效率时,使用本地方法可以加快运行效率

       在这个例子中,在类NativeDemo中,我们声明了一个本地方法add,而在main方法中,创建了NativeDemo类的对象,调用add方法计算两个整数的和。可以发现,我们并没有真正写两数求和的实现代码,这是因为本地方法的实现代码就存在本地库中,无需我们再去编写。

本地方法栈的内存结构

首先我们来看一张结构图,运行时数据区作为虚拟机的一部分,它包含有本地方法栈。

       还是用上面的add方法为例,我们首先声明好本地方法,然后通过在静态代码块中使用System.loadLibrary("NativeDemo");语句把本地方法库加载到java程序中,当main方法中调用add()方法时,虚拟机就会转换为本地方法调用。

本地方法栈的异常管理

       由于本地方法多数是由C和C++直接编写的,然后本地方法在虚拟机上执行,所以虚拟机本身并不支持本地方法栈的异常管理,因为虚拟机无法对非java语言编写的代码管理。因此,虚拟机是通过与操作系统的本地调用机制进行交互,来实现对本地方法中异常的管理

       举个例子,在windows系统中,本地方法通过使用Win API来调用系统函数,并利用操作系统提供的异常处理机制SEH(structured Exception Handling)来管理异常;而在MacOs系统中,也很类似,操作系统通过Mach-0格式来加载本地代码库,虚拟机使用JNI来调用其中的本地方法,在本地方法内,POSIX(Portable Operating System Interface for Unix)标准提供的信号处理机制来处理异常。

总结

       Java 虚拟机的栈包括虚拟机栈和本地方法栈,分别用于执行 Java 方法和本地方法时的数据和指令。它们都有自己的深度限制,并且超过限制时会抛出 StackOverflowError 异常。虚拟机栈和本地方法栈的主要区别在于存储的数据类型和方法调用方式不同。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值