【JVM专题】JVM内存模型深度剖析与优化

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。【JVM专题】JVM内存模型深度剖析与优化,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

课程内容

一、JDK体系结构与跨平台特性介绍

1、JDK体系结构

在这里插入图片描述

  • JDK:即Java Development Kit,翻译为“Java 语言的软件开发工具包(SDK)”
  • JRE:Java Runtime Environment,翻译为“Java运行时环境”,顾名思义,JRE可以让计算机系统运行Java应用程序(Java Application)

2、JDK常用的基础命令描述

  • javac:Java编译器,将Java源代码换成字节码
  • java:Java解释器,直接从类文件执行Java应用程序代码
  • javadoc:根据Java源代码及其说明语句生成的HTML文档
  • jdb:Java调试器,可以逐行地执行程序、设置断点和检查变量
  • javah:产生可以调用Java过程的C过程,或建立能被Java程序调用的C过程的头文件
  • javap:Java反汇编器,显示编译类文件中的可访问功能和数据,同时显示字节代码含义
  • jar:多用途的存档及压缩工具,是个java应用程序,可将多个文件合并为单个JAR归档文件

3、Java语言的跨平台特性

严格来说Java的跨平台不算什么神奇的东西,如果你还记得下载jdk的过程的话,你会回想起,一般jdk有windows版本跟jdk版本。跨平台的本质,其实是jdk在不同系统平台调用对应系统的Api而已。附上一段C/C++跨平台的示例代码:

C++:编写跨平台程序的关键,C/C++中的内置宏定义
分两部分:
 
操作系统判定:
Windows:   WIN32
Linux:   linux
Solaris:   __sun
 
编译器判定:
VC:  _MSC_VER
GCC/G++:   __GNUC__
SunCC:   __SUNPRO_C和__SUNPRO_CC

预编译命令:
#ifdef #endif: 顾名思义,该命令是在编译之前的阶段,作用的if-else。以此实现"选择编译内容"的能力

------------------------------------------------------------------------
#include <stdio.h>
#include <iostream>
using namespace std;
int main(int argc,char **argv)
{
    int no_os_flag=1;
 
    #ifdef linux // 若为Linux环境,则编译以下代码
       no_os_flag=0;
       cout<<"It is in Linux OS!"<<endl;
    #endif


    #ifdef _UNIX // 若为Unix环境,则编译以下代码
       no_os_flag=0;
       cout<<"It is in UNIX OS!"<<endl;
    #endif


    #ifdef __WINDOWS_ // 若为Windows环境,则编译以下代码
       no_os_flag=0;
       cout<<"It is in Windows OS!"<<endl;
    #endif


    #ifdef _WIN32 // 若为Windows环境,则编译以下代码
       no_os_flag=0;
       cout<<"It is in WIN32 OS!"<<endl;
    #endif

    if(1==no_os_flag){
        cout<<"No OS Defined ,I do not know what the os is!"<<endl;
    }
    return 0;
}

在这里插入图片描述

二、JVM内存模型深度剖析

我发现,不少人会以为下图就是JVM的内存模型:
在这里插入图片描述
只能说对了一半,但是不全。在讲解完整的JVM模型之前,先附上一段代码,帮助理解:

public class Math {
	public static int count = 1;
	public static User user = new User();

	public void compute() {
		int a = 1;
		int b = 2;
		int c = (a + b) * 10
		return c;
	}
	public static void main(String[] args) {
		Math math = new Math();
		math.compute();
	}
}

我们来分析一下上面代码,都有哪些元素吧:

  1. 1个类Math
  2. 2个静态变量,分别为int基础类型count,以及new出来的User对象user
  3. 1个main方法
  4. main方法中有1个new出来的局部变量,Math对象math,它应该存放在堆中
  5. 1个局部方法,compute()
  6. compute方法中有3个int类型局部变量

上面提到的【代码元素】,他们在完整的JVM的内存模型的表现如下:
JVM内存模型
如上图所示,我将图片用一条红色箭头划分成了两部分。结合上面提到的代码元素,看看上图右边部分——JVM完整内存模型

  • :是一块用来动态分配内存的,运行时数据区,是java中重要的内存组成部分,它是所有线程共享的。绝大多数java对象、数组都存储在这里。一个典型的现象就是:new出来的对象会被分配在这里。如上图,无论是Math对象,还是静态User对象,都存放在这里。
  • 方法区/元空间(jdk1.8之前也叫做永久代):是一块用来存储常量、静态变量、类元信息的运行时数据区域,它也是所有线程共享的。与堆不同的是,这里存放的数据,是类级别的,而堆是对象级别。(PS:其实说类元信息什么的有点笼统、抽象,更加详细一点的说法是方法区里存放着类的版本,字段,方法,接口和常量池;常量池里存储着字面量和符号引用,我在下面附上两张类元信息解析图)(常量池具体解释,见下面)

类元信息解析图:(由 jsclasslib插件生成)
类元信息解析图
再来看看神秘的常量池:
在这里插入图片描述

  • 栈(线程):java官方又叫虚拟机栈。但是用“线程栈”来描述会比较恰当一点。准确的来说,每一条线程在运行时都会开辟一条自己的“线程栈”,使得不同的线程之间彼此独立。所以,线程栈是用来存放线程在运行时产生的一些数据的内存区域,它是每个线程独有的
  • 本地方法栈:顾名思义,是用来存放本地方法调用链过程的内存区域,它是每个线程独有的。本地方法是什么?即hotspot所有用native修饰的方法。
  • 程序计数器:是一个用来记录代码运行位置的内存区域,它也是每个线程独有的。不同于计算机组成原理中我们知道的程序计数器 ,这边的程序计数器的维护是由JVM的【字节码执行引擎】完成的。它的作用,是用来在线程上下文切换的过程中,保存当前线程状态,方便再次获得CPU时间片的时候“还原现场”。

说完图的右边,我们再来说说图的左边部分——线程栈的模型。根据图片的信息,我们可以发现有一下元素:

  • 栈帧:一个栈帧对应着一个方法的调用。我们知道方法的调用链跟栈FILO的特性天然契合,所以后调用的栈帧会在先调用的栈帧前面。如main方法栈帧,math.compute方法栈帧
  • 局部变量表:存放方法内生成的局部变量。值得注意的是:如果是基础数据类型,则存放的是基础数据;若为引用类型Object数组等,则存放的,是指向堆内存的一个引用。
  • 操作数栈:跟CPU的寄存器是差不多的工作原理,存放的是当前操作的数据内容,同时也有计算能力。栈字,FILO特性也可见一斑,这里必然存在“压栈”、“出栈”对象。这个对象是谁呢?即:局部变量表中的值。
  • 动态链接:说到动态连接,不得不提一下符号引用。符号引用,在这里,即将compute这个方法的符号替换成指向数据所存内存的指针或句柄等直接引用。对于compute方法来说,这个直接引用则是:方法区常量池中,compute()所有符号所在常量池中的地址。(这里用到常量池,只是为了就是为了一些提供符号的,便于指令的识别)。接着在运行时,这些符号会被转变为compute()方法具体代码在内存中的地址,主要是通过对象头的类型指针去转换引用
  • 方法出口:存放调用链某个节点被发起调用时的起始地址

三、GCRoot与STW机制

什么是GCRoot,什么又是STW机制呢?欲知此,我们必须要先知道一个东西,那就是Java的垃圾回收器,GC(Garbage Collector)。
GC的目标区域是哪里呢?堆。说到这里,我们需要补充一下,堆的内存模型。

堆的内存模型以及分代收集理论

在这里插入图片描述
堆分为年轻代老年代,默认的,他们的比例为1:2。而年轻代,通常又分为3个区域,分别为:eden区,survivor0区,survivor1区,他们默认的比例为:8:1:1。他们的联系与区别如下:

  • 年轻代:存放“朝生夕死”的对象,通常我们分配在堆里的新对象,都会先放在年轻代中。在这里发生的GC,叫做MinorGC。
    • eden:翻译,伊甸园。何为伊甸园?即迎接新生的地方,所以这里又叫:新生区。
    • survivor0、survivor1:这两者其实是同一个东西,没什么区别。只不过,因为年轻代采用的GC算法是“复制”算法,所以为了提升复制效率,通常需要一个空的survivor区,来做中间缓存区。
  • 老年代:跟年轻代相反,这里存放的通常都是在前几次(默认15)GC中幸存下来的“老年”对象,所以叫老年代。除了老年对象,这里也可能存放“大对象”,这个在后面会讲解。在这里发生的GC,叫做FullGC/MajorGC。

当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

回到正题,什么是GCRoot跟STW机制呢?这两个东西都是发生在GC过程中的概念。

GCRoot

翻译,即垃圾回收的根节点。它是垃圾回收过程中,用来寻找、标记,垃圾/非垃圾对象的起始点,从它开始遍历,找到所有活动对象并标记它们,将未被标记的对象回收。GCRoot通常为以下对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
  • 方法区中类静态属性引用的对象。例如java类的引用类型静态变量
  • 方法区中常量引用的对象。例如字符串常量池里的引用
  • 本地方法栈中 JNI 引用的对象
  • 被同步锁持有的对象
  • Java 虚拟机内部的引用。如基本数据类型对应的class对象,一些常驻的异常对象等,还有类加载器

STW

翻译,即停止世界(Stop The World),该机制是指在进行垃圾回收时,Java虚拟机会暂停应用程序的运行,以便进行垃圾回收操作。这意味着在进行垃圾回收时,应用程序将无法继续执行。这对于需要实时响应的应用程序来说是一个问题,因为它们需要保持持续的响应时间

四、JVM参数设置通用模型

在这里插入图片描述
先附上一段我当前线上生产应用的jvm参数:

java ‐Xms4096M ‐Xmx4096M ‐Xmn2048M ‐Xss1M ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar server.jar

上面这段jvm参数,涉及到的内容刚好跟上图提到的一模一样。接下来解释一下各个参数的意义。

-Xms4096M:初始化堆内存大小为4096M
-Xmx4096M:堆内存最大值为4096M
-Xmn2048M:设置年轻代大小为2048M。PS:增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
-Xss1M:设置每个线程栈大小为1M。该值设置越小,一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多
-XX:MetaspaceSize=256M:初始化方法区/元空间大小为256M
‐XX:MaxMetaspaceSize=256M:设置方法区/元空间大小最大为256M

五、Class常量池与运行时常量池

学习总结

  1. JVM的内存模型,可以分为运行时数据区+类加载子系统+字节码执行器。而运行时数据区,则包含:堆(线程共享)方法区/元空间(线程共享),线程栈(每个线程独有一片空间),本地方法栈(每个线程独有一片空间),程序计数器(每个线程独有一片空间)。
  2. 线程栈又包含多个栈帧。每调用一个方法,则在线程栈上生成一个栈帧。栈帧是用来存放方法运行中产生的一些数据的。这些数据包括:局部变量、操作数栈、动态链接、方法出口。
  3. 动态链接,跟类加载阶段的静态链接作用一样。只不过后者目标对象是静态方法,而前者则为非静态方法。它是将方法符号,替换为指向数据所存内存的指针或句柄。(读到这里,才发现这东西跟C/C++中提到的虚函数表差不多的意思,后来找到了一篇关于动态链接的博文,算是有点印象了。JVM7:Java虚拟机栈——动态链接(Dynamic Linking)

感谢

感谢【作者:库隐】大佬的博文,JVM7:Java虚拟机栈——动态链接(Dynamic Linking)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/180544.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!