java虚拟机 jvm 局部变量表实战

简介: java局部变量表是栈帧重要组中部分之一。他主要保存函数的参数以及局部的变量信息。局部变量表中的变量作用域是当前调用的函数。函数调用结束后,随着函数栈帧的销毁。

java局部变量表是栈帧重要组中部分之一。他主要保存函数的参数以及局部的变量信息。局部变量表中的变量作用域是当前调用的函数。函数调用结束后,随着函数栈帧的销毁。局部变量表也会随之销毁,释放空间。

由于局部变量表存在栈帧中。所以,如果函数参数和局部变量比较多,会使的局部变量表膨胀,每一次调用会占用更多的栈空间。最终结局就是栈空间内存一定的情况下调用的次数减少。

1.1.1. 局部变量表变量影响

下面的代码演示在栈空间内存一定的情况下,参数以及局部变量的大小对函数调用次数的影响。第一个函数recursion()不包含任何的参数和局部变量,第二个函数recursion()包含3个参数和4个局部变量,因此我们也可以算出局部变量表中包含了13个变量信息。第一个局部变量表拥有更深的调用层次。代码如下:

private static int count=0;
public static void recursion(int a,int b,int c){
long l1=12;
short sl=1;
byte b1=1;
String s="1";
System.out.println("count="+count);
count++;
recursion(1,2,3);
}
public static void recursion(){
System.out.println("count="+count);
count++;
recursion();
}


使用jvm参数-Xss128K执行上面第一个无参的recursion()函数,输出结果如下:

count=4495
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)


使用jvm参数-Xss128K执行上面第二个个有参的recursion()函数,输出结果如下:

count=3865
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:564)
at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:619)


可以得出结论:

在同等的栈容量下,局部变量少的函数可以支持更深的函数调用。调用次数也就越多。

如何证明结论是正确的呢?在这里我们借助jclasslib工具来查看局部变量表的局部变量信息。

下图显示了无参的recursion()函数最大的局部变量表大小为0个字。有参的recursion()函数最大的局部变量表大小为8个字。在这里说明一下:

intshortbyte、对象引用等占用一个字。longdouble局部变量中需要占用2个字。

(字 一个字在32位计算机中为4个字节的长度)

我们来算一下recursion(1,2,3);方法局部变量表的大小。

三个参数为int 所以 是3

long l1=12; 2

short sl=1; 1

byte b1=1; 1

String s="1"; 1

所以说 一共是8个字。

需要强调的一点是这里说的局部变量表指的是java栈空间的一部分,不要跟下面说的classs字节码中的局部变量表混淆。

 

下面展示class字节码中的局部变量表的内容:

 

 

从图中看以看出来一些信息:

Class文件的局部变量表定义中,显示了每一个局部变量的作用范围、所在槽位的索引信息(index列信息)

变量的名称(name)和数据类型信息(descriptor)

数据类型信息映射:

I---int类型

D--double类型

B-byte类型

Ljava/lange/Integer --Integer类型

Ljava/lange/String--String类型

S--short类型

栈帧中的局部变量的槽位是可以重复使用的。如果一个声明的变量过了其作用域,那么其作用域之后申请的变量有可能复用过期的局部变量的槽位,从而能够达到节省资源目的。

1.1.2. 局部变量表槽位的复用

下面得代码显示了局部变量表槽位的复用。localVar1()函数中,局部变量ab得范围都是到了函数的末尾所以b是没有办法复用a的卡槽所在的位置。localVar2()函数中,局部变量a}之后不在有效了,所以b是可以复用a的卡槽的位置都是int类型所以是1个字。程序如下所示:

public void localVar1(){

int a=0;

System.out.println(a);

int b=0;

}

public void localVar2(){

{

int a=0;

System.out.println(a);

}

int b=0;

}

使用jclasslib工具来查看局部变量表的局部变量localVar1()信息如下图:

 


 

 

该函数最大的局部变量大小3个字,卡槽0位为thsi引用(实例方法的第一个局部变量都是this),第一个卡槽位变量为a,第二个卡槽位变量为b,每个变量是1个字所以一共是三个字。

使用jclasslib工具来查看局部变量表的局部变量localVar2()信息如下图:

 


 

该函数最大的局部变量大小2个字,卡槽0位为thsi引用(实例方法的第一个局部变量都是this),第一个卡槽位变量为a,第二个卡槽位变量为b,每个变量是1个字 但是b变量复用了a卡槽位所以一共是2个字。

 

局部变量表也是作为垃圾回收gc的重要参考点,只要被局部变量表中直接或者间接引用的对象都不会被回收。所以必须要理解局部变量表才能理解gc回收机制。

下面的主要讲解说明局部变量对垃圾回收的影响。

1.1.3. 局部变量对垃圾回收的影响

程序代码如下所示:

jvm参数-XX:+PrintGC参数 垃圾回收前后堆得大小

JvmTestLocalVarGc t=new JvmTestLocalVarGc();

t.localvarGc1();

结果输出:[Full GC 3875K->3546K(15872K), 0.0050719 secs]

在申请空间后,立即调用GC垃圾回收,很明显,由于byteb强引用所以无法回收这块空间。

JvmTestLocalVarGc t=new JvmTestLocalVarGc();

t.localvarGc2();

结果输出:[Full GC 3875K->474K(15872K), 0.0036066 secs]

在垃圾回收前,现将b设置为null,使byte数组拾取引用,所以GC后byte数组被直接垃圾回收了。

JvmTestLocalVarGc t=new JvmTestLocalVarGc();

t.localvarGc3();

结果输出:[Full GC 3875K->3546K(15872K), 0.0069622 secs]

在进行垃圾回收前,先使局部变量b实现,虽然b离开了作用域,但是变量b亦然存放在局部变量表中。并且指向byte数组,故byte数组亦然没有被回收。

JvmTestLocalVarGc t=new JvmTestLocalVarGc();

t.localvarGc4();

结果输出:[Full GC 3875K->474K(15872K), 0.0037666 secs]

在垃圾回收前,不仅是b失效了,c复用了变量b的字,由于b被销毁,所以byte数组被销毁了。

JvmTestLocalVarGc t=new JvmTestLocalVarGc();

t.localvarGc5();

结果输出:[Full GC 3875K->3546K(15872K), 0.0054367 secs]

[Full GC 3546K->474K(15936K), 0.0036164 secs]

对于localvarGc5()调用localvarGc1()方法,很明显localvarGc1()中没有回收byte数组,但在其返回后他的栈帧被销毁了,自然栈帧中所有的局部变量也没销毁了,容器没了,值当然也不存在了嘛。

所以byte数组失去饮用。在localvarGc5()中被回收了。

          

相关文章
|
2月前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
51 0
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
1月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
2月前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
62 1
|
12天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
62 17
|
22天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
7天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
24天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
24天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。