zoukankan      html  css  js  c++  java
  • 深入理解JVM2

    1 JVM简介

    VM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

    Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

    2 JVM的组成部分

    我们先把JVM这个虚拟机画出来,如下图所示:

     

    从这个图中可以看到,JVM是运行在操作系统之上的,它与硬件没有直接的交互。JVM有哪些组成部分,如下图所示:

    整个JVM分为四部分:

    • Class Loader 类加载器

    类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java程序,然后通过javac编译成class文件,那怎么才能加载到内存中被执行呢?Class Loader承担的就是这个责任,那不可能随便建立一个.class文件就能被加载的,Class Loader加载的class文件是有格式要求,在《JVM Specification》中式这样定义Class文件的结构:

    在这张图中,每一行表示两个字节长度,按照从上到下、从左到右的顺序描述了class文件的结构。其中,浅颜色的部分是无符号数,深颜色的部分是表。下面以表格的形式详细描述一下具体的信息:

    类型

    名称

    数量

    U4

    magic

    1

    U2

    minor_version

    1

    U2

    major_version

    1

    U2

    constant_pool_count

    1

    cp_info

    constant_pool

    constant_pool_count-1

    U2

    access_flags

    1

    U2

    this_class

    1

    U2

    super_class

    1

    U2

    interfaces_count

    1

    U2

    interfaces

    interfaces_count

    U2

    fields_count

    1

    field_info

    fields

    fields_count

    U2

    methods_count

    1

    method_info

    methods

    methods_count

    U2

    attributes_count

    1

    attribute_info

    attributes

    attributes_count

    Class Loader只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution Engine负责的。

    • Execution Engine 执行引擎

    执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。

    • Native Interface本地接口

    本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见

    • Runtime data area运行数据区

    运行数据区是整个JVM的重点。所有写的程序都被加载到这里,之后才开始运行

    整个JVM框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,一个完整的系统诞生了!

    2 JVM的内存管理

    所有的数据和程序都是在运行数据区存放,它包括以下几部分:

    • Stack 

    栈也叫栈内存,是Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。问题出来了:栈中存的是那些数据呢?又什么是格式呢?

    栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循先进后出原则。

    那栈帧中到底存在着什么数据呢?栈帧中主要保存3类数据:本地变量(Local Variables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(Frame Data),包括类文件、方法等等。光说比较枯燥,我们画个图来理解一下Java栈,如下图所示:

    图示在一个栈中有两个栈帧,栈帧2是最先被调用的方法,先入栈,然后方法2又调用了方法1,栈帧1处于栈顶的位置,栈帧2处于栈底,执行完毕后,依次弹出栈帧1和栈帧2,线程结束,栈释放。

    • Heap 堆内存

    一个JVM实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:

    Permanent Space 永久存储区

    永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。

    Young Generation Space 新生区

    新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。

    Tenure generation space养老区

    养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。   三个区的示意图如下:

    •  Method Area 方法区

    方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。

    • PC Register 程序计数器

    每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。

    • Native Method Stack 本地方法栈

    3 JVM相关问题

    问:堆和栈有什么区别

    答:堆是存放对象的,但是对象内的临时变量是存在栈内存中

    栈是跟随线程的,有线程就有栈,堆是跟随JVM的,有JVM就有堆内存。

    问:堆内存中到底存在着什么东西?

    答:对象,包括对象变量以及对象方法。

    问:类变量和实例变量有什么区别?

    答:静态变量是类变量,非静态变量是实例变量,有static修饰的变量是静态变量,没有static修饰的变量是实例变量。静态变量存在方法区中,实例变量存在堆内存中。

    问:Java的方法(函数)到底是传值还是传址?

    答:都不是,是以传值的方式传递地址,具体的说原生数据类型传递的值,引用类型传递的地址。对于原始数据类型,JVM的处理方法是从Method AreaHeap中拷贝到Stack,然后运行frame中的方法,运行完毕后再把变量指拷贝回去。

    问:为什么会产生OutOfMemory产生?

    答:Heap内存中没有足够的可用内存了。这句话要好好理解,不是说Heap没有内存了,是说新申请内存的对象大于Heap空闲内存,比如现在Heap还空闲1M,但是新申请的内存需要1.1M,于是就会报OutOfMemory了,可能以后的对象申请的内存都只要0.9M,于是就只出现一次OutOfMemoryGC也正常了,看起来像偶发事件,就是这么回事。 但如果此时GC没有回收就会产生挂起情况,系统不响应了。

    问:产生的对象不多呀,为什么还会产生OutOfMemory

    答:继承层次忒多了,Heap 产生的对象是先产生 父类,然后才产生子类。

    问:OutOfMemory错误分几种?

    答:分两种,分别是“OutOfMemoryError:java heap size””OutOfMemoryError: PermGen space”,两种都是内存溢出,heap size是说申请不到新的内存了,这个很常见,检查应用或调整堆内存大小。

    “PermGen space”是因为永久存储区满了,这个也很常见,一般在热发布的环境中出现,是因为每次发布应用系统都不重启,久而久之永久存储区中的死对象太多导致新对象无法申请内存,一般重新启动一下即可。

    问:为什么会产生StackOverflowError

    答:因为一个线程把Stack内存全部耗尽了,一般是递归函数造成的。

    问:一个机器上可以看多个JVM吗?JVM之间可以互访吗?

    答:可以多个JVM,只要机器承受得了。JVM之间是不可以互访,不能在A-JVM中访问B-JVMHeap内存,这是不可能的。在以前老版本的JVM中,会出现A-JVM Crack后影响到B-JVM,现在版本非常少见。

    问:JVM中到底哪些区域是共享的?哪些是私有的?

    答:HeapMethod Area是共享的,其他都是私有的,

    问:什么是JIT

    答:JIT是指Just In Time,有的文档把JIT作为JVM的一个部件来介绍,有的是作为执行引擎的一部分来介绍,这都能理解。Java刚诞生的时候是一个解释性语言,即使编译成了字节码(byte code)也是针对JVM的,它需要再次翻译成原生代码(native code)才能被机器执行,于是效率的担忧就提出来了。Sun为了解决该问题提出了一套新的机制,好,你想编译成原生代码,没问题,我在JVM上提供一个工具,把字节码编译成原生码,下次你来访问的时候直接访问原生码就成了,于是JIT就诞生了,就这么回事。

    问:JVM还有哪些部分是没有提到的?

    答:JVM是一个异常复杂的东西,写一本砖头书都不为过,还有几个要说明的:

    常量池(constant pool):按照顺序存放程序中的常量,并且进行索引编号的区域。比如int i =100,这个100就放在常量池中。

    安全管理器(Security Manager):提供Java运行期的安全控制,防止恶意攻击,比如指定读取文件,写入文件权限,网络访问,创建进程等等,Class LoaderSecurity Manager认证通过后才能加载class文件的。

    方法索引表(Methods table),记录的是每个method的地址信息,StackHeap中的地址指针其实是指向Methods table地址。

    问:为什么不建议在程序中显式的生命System.gc()

    答:因为显式声明是做堆内存全扫描,也就是Full GC,是需要停止所有的活动的(Stop  The World Collection)。

  • 相关阅读:
    CentOS查看CPU信息、位数、多核信息
    Linux常用命令大全
    chmod命令详细用法
    tar命令的详细解释
    yum和rpm命令详解
    LeetCode 241. Different Ways to Add Parentheses
    LeetCode 139. Word Break
    LeetCode 201. Bitwise AND of Numbers Range
    LeetCode 486. Predict the Winner
    LeetCode 17. Letter Combinations of a Phone Number
  • 原文地址:https://www.cnblogs.com/aaron911/p/7875057.html
Copyright © 2011-2022 走看看