第5章 JVM调优

5.1 Java虚拟机内存模型

Java虚拟机内存模型是Java程序运行的基础。JVM将其内存数据分为程序计数器,虚拟机栈,本地方法栈,Java堆和方法区等部分。
程序计数器:用于存放下一条运行的指令;
虚拟机栈和本地方法栈:用于存放函数调用堆栈信息;
Java堆:用于存放Java程序运行时所需的对象等数据;
方法区:用于存放程序的类元数据信息;

5.1.1 程序计数器

程序计数器是一块很小内存空间。由于Java是支持线程的语言,当线程数量超过CPU数量时,线程之间根据时间片轮询抢夺CPU资源。对于单核CPU而言,每一时刻,只能有一个线程在运行,而其他线程必须被切换出去。为此,每一个线程都必须用一个独立的程序计数器,用于记录下一条要运行的指令。各个线程之间的计数器互不影响,独立工作,是一块线程私有的内存空间。

如果当前线程正在执行一个Java方法,则程序计数器记录正在执行的Java字节码地址,如果当前线程正在执行一个Native方法,程序计数器为空。

5.1.2 Java虚拟机栈

Java虚拟机栈也是线程私有的内存空间,它和Java线程在同一时间创建,它保存方法的局部变量,部分结果,并参与方法的调用与返回。

Java虚拟机规范允许Java栈的大小是动态的或者是固定的。在Java虚拟机规范中,定义了两种异常与栈空间有关:StackOverflowError和OutOfMemoryError。如果线程在计算过程中,请求的栈深度大于最大可用的栈深度,则抛出StackOverflowError;如果Java栈可以动态扩展,而在扩展栈的过程中,没有足够的内存空间来支持栈的扩展,则抛出OutOfMemoryError。

在HotSpot虚拟机中,可以使用-Xss参数来设置栈的大小。栈的大小直接决定了函数调用的可达深度。

虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中,存放了方法的局部变量表,操作数栈,动态连接方法和返回地址等信息。每一个方法的调用都伴随着栈帧的入栈操作。相应地,方法的返回则表示栈帧的出栈操作。如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中的局部变量表就会比较大,栈帧会膨胀以满足方法调用所需传递的信息。因此,单个方法调用所需的栈空间大小也会比较多。

注意:函数嵌套调用的次数由栈的大小决定。栈越大,函数嵌套调用次数越多。对一个函数而言,它的参数越多,内部局部变量越多,它的栈帧就越大,其嵌套调用次数就会越少。

在栈帧中,与性能调优关系最为密切的部分就是局部变量表。局部变量表用于存放方法的参数和方法内部的局部变量。局部变量表以“字”为单位进行内存的划分,一个字为32位长度。对于long和double型的变量,则占用2个字,其余类型使用1个字。在方法执行时,虚拟机使用局部变量表完成方法的传递,对于非static方法,虚拟机还会将当前对象(this)作为参数通过局部变量表传递给当前方法。

注意:使用jclasslib工具可以深入研究Class类文件的结构,有助于读者对Java语言更深入的了解。

局部变量表的字空间是可以重用的。因为在一个方法体内,局部变量的作用范围并不一定是整个方法体。

// 最大局部变量表容量:2+1=3
public void test1() {
    {
        long a = 0;
    }
    long b = 0;
}
// 最大局部变量表容量:2+2+1=3
public void test2() {
    long a = 0;
    long b = 0;
}

局部变量表的字,对系统GC也有一定影响。如果一个局部变量被保存在局部变量表中,那么GC根就能引用到这个局部变量所指向的内存空间,从而在GC时,无法回收这部分空间。

// GC无法回收,因为b还在局部变量表中
public static void test1() {
    {
        byte[] b = new byte[6*1024*1024];
    }
    System.gc();
    System.out.println("first explict gc over");
}
// GC可以回收,因为赋值为null将销毁局部变量表中的数据
public static void test1() {
    {
        byte[] b = new byte[6*1024*1024];
        b = null;
    }
    System.gc();
    System.out.println("first explict gc over");
}

// GC可以回收,因为变量a复用了b的字,GC根无法找到b
public static void test1() {
    {
        byte[] b = new byte[6*1024*1024];
    }
    int a = 0;
    System.gc();
    System.out.println("first explict gc over");
}

// GC无法回收,因为变量a复用了c的字,b仍然存在
public static void test1() {
    {
        int c = 0;
        byte[] b = new byte[6*1024*1024];
    }
    int a = 0; // 复用c的字
    System.gc();
    System.out.println("first explict gc over");
}

// GC可以回收,因为变量d复用了b的字
public static void test1() {
    {
        int c = 0;
        byte[] b = new byte[6*1024*1024];
    }
    int a = 0; // 复用c的字
    int d = 0; // 复用b的字
    System.gc();
    System.out.println("first explict gc over");
}

在这个函数体内,即使在变量b失效后,又未能定义足够多的局部变量来复用该变量所占的字,变量b仍会在该栈帧的局部变量表中。因此GC根可以引用到该内存块,阻碍了其回收过程。在这种环境下,手工对要释放的变量赋值为null,是一种有效的做法。

当方法一结束,该方法的栈帧就会被销毁,即栈帧中的局部变量表也被销毁。

注意:局部变量表中的字可能会影响GC回收。如果这个字没有被后续代码复用,那么它所引用的对象不会被GC释放。

5.1.3 本地方法栈

时间: 2024-10-29 19:11:51

第5章 JVM调优的相关文章

第九章 JVM调优推荐

说明:本文主要参考自<分布式Java应用:基础与实践> 1.JVM的调优主要是内存的调优,主要调两个方面: 各个代的大小 垃圾收集器选择 2.各个代的大小 常用的调节参数 -Xmx -Xms -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold -XX:PermSize -XX:MaxPermSize 原则 -Xmx==-Xms:防止堆内存频繁进行调整,调整的时机见<第一章 JVM内存结构> -Xmn:通常设为-Xmx/4(这是我在企业中实

《Java程序性能优化》学习笔记 Ⅳ JVM调优

第五章 JVM调优5.1 Java虚拟机内存模型1.JVM虚拟机将其内存数据分为程序计数器.虚拟机栈,本地方法栈,Java堆,和方法去等部分.5.2 JVM内存分配参数5.3 垃圾收集基础5.4 常用调优案例和方法5.5 使用JVM参数5.6 实战JVM调优 <Java程序性能优化>学习笔记 Ⅳ JVM调优

Java虚拟机(五):JVM调优-命令篇

运用jvm自带的命令可以方便的在生产监控和打印堆栈的日志信息帮忙我们来定位问题!虽然jvm调优成熟的工具已经有很多:jconsole.大名鼎鼎的VisualVM,IBM的Memory Analyzer等等,但是在生产环境出现问题的时候,一方面工具的使用会有所限制,另一方面喜欢装X的我们,总喜欢在出现问题的时候在终端输入一些命令来解决.所有的工具几乎都是依赖于jdk的接口和底层的这些命令,研究这些命令的使用也让我们更能了解jvm构成和特性. Sun JDK监控和故障处理命令有jps jstat j

生产环境下JVM调优参数的设置实例

JVM基础:生产环境参数实例及分析 原始配置: -Xms128m -Xmx128m -XX:NewSize=64m -XX:PermSize=64m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=78 -XX:ThreadStackSize=128k-Xloggc:logs/gc.log -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.rmi.dgc.client.gcInterv

JVM调优-新生代

JAVA虚拟机新生代,包括eden space+2个survivor空间. 新生代用来存放新近创建的对象,新生代的特点是对象更新速度快,在短时间内产生大量的"死亡对象".对年轻代的垃圾回收称作次级回收 (minor gc) 1.新生代与次级回收 新生代分为三个区域,  一个eden spac , 2个大小相同的survivor,  应用程序只能使用一个eden和一个survivor, 当发生初级垃圾回收的时候,gc挂起程序, 然后将eden和survivorA中的存活对象复制到另外一个

JVM调优经验分享

前言 一.JVM调优知识背景简介 二.JVM调优参数简介 三.JVM调优目标 四.JVM调优经验 结束语 <br/> 本次分享探讨的JVM调优是指server端运行的JVM调优,适应版本为[1.6– 1.7], 不涉及最新的1.8版本. 假设线程池.连接池.程序代码等都已经做过优化,效果(系统吞吐量.响应性能)仍然不理想,我们就可以考虑JVM调优了. <br/> 一. JVM调优知识背景简介 1.堆与栈的概念 堆和栈是程序运行的关键:栈是运行时的单位,而堆是存储的单位. 栈解决程序

JVM调优

转自:http://blog.csdn.net/chen77716/article/details/5695893 一.JVM内存模型及垃圾收集算法 1.根据Java虚拟机规范,JVM将内存划分为: New(年轻代) Tenured(年老代) 永久代(Perm) 其中New和Tenured属于堆内存,堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但可以通过-XX:PermSize -XX:MaxPermSize 等参数调整其大小. 年轻代(N

Spark性能调优之JVM调优

Spark性能调优之JVM调优 通过一张图让你明白以下四个问题 1.JVM GC机制,堆内存的组成                2.Spark的调优为什么会和JVM的调优会有关联?--因为Scala也是基于JVM运行的语言                3.Spark中OOM产生的原因                4.如何在JVM这个层面上来对Spark进行调优 补充:                Spark程序运行时--JVM堆内存分配比例 RDD缓存的数据(0.6)    默认 对象_

Tomcat的JVM调优实战

一些调优点在上篇日志中已写到,在此不做说明 直接使用Jmeter进行调优测试吞吐量Code package cn; import java.io.IOException; import java.util.Map; import java.util.WeakHashMap; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.Htt