热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JVM调优之:内存模型

2019独角兽企业重金招聘Python工程师标准1.JVM内存模型2.程序计数器程序计数器是一个很小的内存空间。由于Java是支持多线程的语音,当线程数量超过c

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

1. JVM内存模型

220142_t0Np_3100849.png

2.程序计数器

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

如果当前线程正在执行一个Java方法,程序计数器中存放的就是正在执行的Java字节码地址,如果正在执行的是native方法,则程序计数器为空。

3.虚拟机栈

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

JAVA规范中允许Java栈的大小是动态的或者是固定不变的;Java虚拟机中定义了两种异常与栈空间有关

StackOverflowError:如果线程在计算过程中,请求的栈深度大于最大可用的栈深度,则抛出该错误。

OutOfMemoryError:如果Java栈可以动态扩展,在扩展过程中没有足够的内存空间来支持栈的扩展,则抛出该错误。

JVM中可是使用-Xss参数来设置栈大小,栈的大小直接决定了函数调用的可达深度。以下代码通过递归调用查看方法调用可达深度。在JDK5.0及其以前栈默认大小为256k,JDK5.0之后jvm栈默认大小为1m。

public class StackTest {private int count = 0;//记录栈可达深度public void recursion(){count ++;recursion();}public static void main(String[] args) {StackTest stackTest = new StackTest();try {stackTest.recursion();}catch (Throwable e){System.out.println("deep of stack is "+stackTest.count);e.printStackTrace();}}
}

默认执行结果:

deep of stack is 17127
java.lang.StackOverflowError

如果系统需要更深的栈调用,设置-Xss2m

执行结果:

deep of stack is 73391
java.lang.StackOverflowError

可以看到增加栈空间后,函数调用的栈深度明显增加。

虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据,在栈帧中存放了方法局部变量表、操作数栈、动态连接方法和返回地址等信息。每个方法的调用都伴随着入栈,方法的返回伴随着出栈。如果方法调用时方法的参数和局部变量越多那么栈帧中局部变量表就越大,栈帧占用的空间就越大,那么方法调用嵌套次数就越小。

如下方法和上面例子比较

public class StackTest {private int count = 0;//记录栈可达深度public void recursion(long a,long b,long c){long d = 0,f = 0,e = 0;count ++;recursion(d,f,e);}public static void main(String[] args) {StackTest stackTest = new StackTest();try {stackTest.recursion(1L,2L,3L);}catch (Throwable e){System.out.println("deep of stack is "+stackTest.count);e.printStackTrace();}}
}

默认栈大小-Xss1m时执行结果,可以看到当方法参数和局部变量增加时,调用深度明显减小。

deep of stack is 5445
java.lang.StackOverflowError

在栈帧中,与性能调优关系最为密切的的部分就是局部变量表。局部变量表存放方法参数和内部变量,非static方法虚拟机还会把当前对象(this)作为参数通过局部变量表传递给当前方法。

通过jclasslib工具可以查看class文件中每个方法所分配的最大局部变量表的容量。打开StackTest.calss文件找到方法recursion(),将其展开后查看Code属性,选择Misc页面,可以查看该方法最大局部变量。局部变量表以“字”为单位进行划分内存空间。long/double类型占两个“字”,其他类型占一个“字”。

//共13个“字”
public void recursion(long a,long b,long c){long d = 0,e = 0, f = 0;count ++;recursion(d,e,f);
}

220854_6WFO_3100849.png

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

public void test1(){{long a = 0;}long b = 0;
}

221154_X5ao_3100849.png

比较

public void test2(){long a = 0;long b = 0;
}

221230_qtXc_3100849.png

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

1. 

public static void test1(){{Byte[] b = new Byte[1024*1024*6];}System.gc();System.out.println("first explict gc over");
}

虽然系统GC时已经超出了b变量作用范围,但是不会被回收

[GC (System.gc())  26542K->25256K(62976K), 0.0043875 secs]
[Full GC (System.gc())  25256K->25191K(62976K), 0.0326676 secs]
first explict gc over

2. 

public static void test1(){{Byte[] b = new Byte[1024*1024*6];b = null;}System.gc();System.out.println("first explict gc over");
}

手动把b变量设置为null,可以使GC时回收b所占用的内存空间。

[GC (System.gc())  26542K->25328K(62976K), 0.0020175 secs]
[Full GC (System.gc())  25328K->627K(62976K), 0.0074723 secs]
first explict gc over

3. 

public static void test1(){{Byte[] b = new Byte[1024*1024*6];}int a = 0;System.gc();System.out.println("first explict gc over");
}

更多的方法是新声明的变量,会复用变量b的字,使b所占的内存空间被GC回收。

[GC (System.gc())  26542K->25384K(62976K), 0.0015061 secs]
[Full GC (System.gc())  25384K->627K(62976K), 0.0077682 secs]
first explict gc over

4. 本地方法栈

本地方法栈和Java虚拟机栈的功能很相似,Java虚拟机栈用于管理Java函数的调用,而本地方法栈用于管理本地方法的调用。因此和Java虚拟机栈一样本地方法栈也会抛出StackOverflowError和OutOfMemoryError。

本地方法栈是使用C语言实现的,在SUN的Hot Spot虚拟机中不区分本地方法栈和Java虚拟中栈。

5. Java堆

Java堆可以说是Java运行时内存中最为重要的部分,几乎所有的对象和数组都在堆中分配空间。Java堆分为新生代和老年代两部分,新生代用来存放新产生的对象和年轻的对象,如果一个对象经过多次GC都没有被回收,则该对象会被存放到老年代。

新生代

Eden:刚刚产生的对象存放该空间。

s0(from space)/s1(to space):至少经过一次GC没有被回收的对象存放在该空间。

老年代:多次GC都没有被回收的对象最终会被存放在该空间。

221315_WqWA_3100849.png

6. 方法区(JDK6)

方法区也是JVM内存中非常重要的的一块内存区域。与堆空间类似,它也是被JVM中所以的线程共享。方法区中主要保存的信息是类的元数据。

方法区存放

类的类型信息:包括类的完整名称,父类的完整名称,类型修饰符,类型的直接接口类表。

常量池:包括这个类方法、域等信息所引用的常量信息。

域信息:包括域名称,域类型和修饰符。

方法信息:包括方法名称,返回类型,方法参数,方法修饰符,方法字节码,操作数栈和方法帧栈的局部变量区大小以及异常表。

在Hot Spot虚拟机中,方法区也成为永久区,是一块独立的内存空间。虽然叫做永久区,但是在永久区中的对象也是可以被回收的。对永久区的回收主要从两个方面分析:一是GC对永久区常量池的回收;二是永久区对类元数据的回收。Hot Spot虚拟机对常量池的回收,只要常量池中的常量没有被任何地方引用就可以被回收。

常量池演示:

String.intern():如果常量池存在当前String,返回常量池中的String;如果常量池中不存在当前String,将当前String添加到常量池,并返回池中对象。

public static void main(String[] args) {for (int i = 0;i}

使用JVM参数-XX:PermSize=2M -XX:MaxPermSize=4M -XX:+PrintGCDetails运行,每当常量池满时就进行回收,确保程序正常运行。

结果显示,当常量池空间不足时,没有被引用的常量会被回收。

元数据演示:

与常量池的回收相比,类的元数据回收,稍微复杂一些,使用javassist类库,产生大量类占用元数据。观察元数据的回收情况。

动态类父类,生成的子类都要继承给父类

//定义演示动态类的父类,后面使用Javassist产生的动态类都是该类的子类
public class JavaBeanObject{private String name="java";public String getName() {return name;}public void setName(String name) {this.name = name;}
}

动态类生成的

public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {for (int i=0;i}

运行参数:-XX:PermSize=2M -XX:MaxPermSize=4M -XX:+PrintGCDetails

以上代码运行会产生大量的JavaBeanObject类的子类,占用元数据,导致永久区空间不足,运行一段时间之后会抛出“java.lang.OutOfMemoryError:PermGen space”显示持久带溢出。

事实上类元数据也是可以被回收的,需要满足以下两个条件:1.该类的所有实例均已经被回收;2.该类的加载器ClassLoader也已经被回收。

7.  方法区(JDK8)

在Java7之前,HotSpot虚拟机中将GC分代收集扩展到了方法区,使用永久代来实现了方法区。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。但是在之后的HotSpot虚拟机实现中,逐渐开始将方法区从永久代移除。

Java7中已经将运行时常量池从永久代移除,在Java 堆(Heap)中开辟了一块区域存放运行时常量池。而在Java8中,已经彻底没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫做元空间。 

总之:jdk1,6常量池放在方法区,jdk1.7常量池放在堆内存,jdk1.8放在元空间里面,和堆相独立。

验证常量池

同样的方法验证元数据

//定义演示动态类的父类,后面使用Javassist产生的动态类都是该类的子类
public class JavaBeanObject{private String name="java";public String getName() {return name;}public void setName(String name) {this.name = name;}
}

动态创建类方法

public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException {for (int i=0;i}

运行参数:-XX:MaxMetaspaceSize=8m  -XX:+PrintGCDetails

限制元空间大小,在不断创建元数据时元空间很快就会被占用完就会抛出异常

[GC (Last ditch collection) [PSYoungGen: 0K->0K(44544K)] 8631K->8631K(132608K), 0.0075906 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(44544K)] [ParOldGen: 8631K->8631K(127488K)] 8631K->8631K(172032K), [Metaspace: 7758K->7758K(1056768K)], 0.0336065 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
Heap
 PSYoungGen      total 44544K, used 1662K [0x00000000eb180000, 0x00000000eee80000, 0x0000000100000000)
  eden space 41984K, 3% used [0x00000000eb180000,0x00000000eb31fab0,0x00000000eda80000)
  from space 2560K, 0% used [0x00000000edd00000,0x00000000edd00000,0x00000000edf80000)
  to   space 10240K, 0% used [0x00000000ee480000,0x00000000ee480000,0x00000000eee80000)
 ParOldGen       total 127488K, used 8631K [0x00000000c1400000, 0x00000000c9080000, 0x00000000eb180000)
  object space 127488K, 6% used [0x00000000c1400000,0x00000000c1c6de78,0x00000000c9080000)
 Metaspace       used 7790K, capacity 8098K, committed 8192K, reserved 1056768K
  class space    used 1936K, capacity 1997K, committed 2048K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
    at javassist.ClassPool.toClass(ClassPool.java:1099)
    at javassist.ClassPool.toClass(ClassPool.java:1042)
    at javassist.ClassPool.toClass(ClassPool.java:1000)
    at javassist.CtClass.toClass(CtClass.java:1224)

JDK8之后内存模型图

002109_ziX9_3100849.png


转:https://my.oschina.net/u/3100849/blog/885697



推荐阅读
  • 本题库精选了Java核心知识点的练习题,旨在帮助学习者巩固和检验对Java理论基础的掌握。其中,选择题部分涵盖了访问控制权限等关键概念,例如,Java语言中仅允许子类或同一包内的类访问的访问权限为protected。此外,题库还包括其他重要知识点,如异常处理、多线程、集合框架等,全面覆盖Java编程的核心内容。 ... [详细]
  • Python与R语言在功能和应用场景上各有优势。尽管R语言在统计分析和数据可视化方面具有更强的专业性,但Python作为一种通用编程语言,适用于更广泛的领域,包括Web开发、自动化脚本和机器学习等。对于初学者而言,Python的学习曲线更为平缓,上手更加容易。此外,Python拥有庞大的社区支持和丰富的第三方库,使其在实际应用中更具灵活性和扩展性。 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • 本文详细探讨了C语言中`extern`关键字的简易编译方法,并深入解析了预编译、`static`和`extern`的综合应用。通过具体的代码示例,介绍了如何在不同的文件之间共享变量和函数声明,以及这些关键字在编译过程中的作用和影响。文章还讨论了预编译过程中宏定义的使用,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • PHP中元素的计量单位是什么? ... [详细]
  • 设计模式深度解析:桥接模式的应用与实现
    设计模式深度解析:桥接模式的应用与实现 ... [详细]
  • Java服务问题快速定位与解决策略全面指南 ... [详细]
  • 计算 n 叉树中各节点子树的叶节点数量分析 ... [详细]
  • 在进行网络编程时,准确获取本地主机的IP地址是一项基本但重要的任务。Winsock作为20世纪90年代初由Microsoft与多家公司共同制定的Windows平台网络编程接口,为开发者提供了一套高效且易用的工具。通过Winsock,开发者可以轻松实现网络通信功能,并准确获取本地主机的IP地址,从而确保应用程序在网络环境中的稳定运行。此外,了解Winsock的工作原理及其API函数的使用方法,有助于提高开发效率和代码质量。 ... [详细]
  • 本文深入探讨了JVM的核心机制,重点解析了堆内存与栈内存的功能与特性。JVM栈主要负责程序的执行流程,包括方法调用和数据处理;而JVM堆则专注于数据的存储管理,主要用于存放对象实例。栈内存中存储的是基本数据类型以及堆中对象的引用,确保了程序在运行时能够高效地访问和操作数据。 ... [详细]
  • 如何将PHP文件上传至服务器及正确配置服务器地址 ... [详细]
  • C++ 进阶:类的内存布局与虚函数类的实现细节
    C++ 进阶:类的内存布局与虚函数类的实现细节 ... [详细]
  • 工厂方法模式详解:莫勇鹏老师的深入解析与应用实例
    2019年,独角兽企业高薪招聘Python工程师时特别关注工厂方法模式(Factory Method Pattern)。该模式通过定义一个创建对象的接口,让子类决定实例化哪一个类。莫勇鹏老师深入解析了这一设计模式,并提供了丰富的应用实例,帮助开发者更好地理解和运用工厂方法模式,提升软件设计的灵活性和可扩展性。 ... [详细]
  • 如何在 Java LinkedHashMap 中高效地提取首个或末尾的键值对? ... [详细]
author-avatar
深厚科学修养的文艺青年
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有