JVM简介(一)

本文主要内容

  • JVM组成
  • 垃圾回收机制简介

本文不涉及的内容

  • 编译技术
  • JVM中class文件结构
  • JVM线程同步机制
  • JVM指令集构成
  • 类加载流程及其生命周期
  • 对象实例化过程
  • 对象在堆中的表示方法

这些以后慢慢填吧

JVM与我们的关系

广义上来讲,JVM指的是一种设计规范

jvm_1.png

从图中我们可以看到,Java编译器将.java文件编译成class文件,交给JVM解析和执行。只要满足这个要求的我们都可以称其为Java虚拟机

JVM构成

JVM整体结构可以由下图展示

jvm_2.png

其中,当JVM加载类文件时,就会将类的相关信息放在方法区(method area)中,程序执行过程中所有对象会放在堆中

jvm_3.png

Class loader subsystem

整个class loader system大致如下图

jvm_4.png

Class loader分为两类

  • Primordial class loader 这个与JVM实现有关,有且仅有一个
  • class loader 对象,不同的classloaders加载的类会放在不同的命名空间中(不同的 classloader 加载同一个类不会被 JVM 认为是同一个类

class loader 的对象和 Class 的对象都在堆中,加载的类型的数据在方法区。

每一个 class loader对象维护一个自己的命名空间,因此一个完整的类名是不足以证明该类的唯一性的-->热更新的手段之一。

class loader的设计是java实现动态加载类的基础-->安卓插件化技术

Method Area

在JVM中,加载的类型(类)是存放在方法区的。所有线程都共享方法区

JVM对于每种类型都会存储一系列的信息:

  • 类的全名
  • 类的直接父类(除非是接口或java.lang.Object)
  • 是类还是接口
  • 类的标识符(public, abstract, final)
  • 实现的接口的list
  • 类的常量池(constant pool)
  • 成员变量信息
  • 方法信息
  • 类变量信息(Class Variables)
  • Classloader 引用(加载此类的class loader)
  • 一个Class类实例引用

类的常量池是一个有序表,持有所有的常量(字符串,整数,浮点数),以及其他类,作用域,方法的符号引用。是Java程序实现动态链接的关键部分

方法信息存储了方法名,方法返回的类型,参数表,方法的修饰符(public, private, protected, static, final, synchronized, native, abstract)
如果方法不是抽象方法,那么还会额外存储方法的字节码,异常表,操作数栈大小和本地变量

类变量(Class Variables) 就是存储了和类相关的变量(static),但有final修饰时情况不同,final static 修饰的变量同时会拷贝一份存储在任何其他使用它的类的数据里,并且是在编译期完成的复制(compile time vs runtime)

当然很多JVM在方法区会加入其他的数据结构来加快执行速度

下面举一个例子:

class Lava {
    private int speed = 5; 
    void flow() {
    } 
}

class Volcano {
    public static void main(String[] args) { 
        Lava lava = new Lava();
        lava.flow();
    } 
}

下面展示的是JVM一种处理方法区的手段。

首先,你将Volcano这个名字告诉给JVM,JVM找到这个名字对应的类文件Volcano.class,JVM读取这个类然后将信息加载到方法区。虚拟机之后调用main函数,即解释方法区中main方法的字节码。此时Lava类还没有被加载,于此同时,虚拟机会维护一个指针指向当前的类常量池(Volcano)

main方法中首先请求新建一个Lava实例,JVM会从Volcano这个类的常量池的第一个元素开始遍历,找到一个指向Lava这个类的符号引用。此时JVM会检查Lava这个类的信息有没有加载进方法区。显然Lava类还没有加载进来,因此它会去寻找并读取Lava.class,将信息提取到方法区中。

之后,JVM会将刚才Volcano中常量池的符号引用,字符串"Lava",替换为一个指向Lava类信息的指针。那么之后JVM如果再次碰到了使用Lava的情况,就会直接使用这个指针去访问Lava类的信息。这个过程被称为constant pool resolution.

直到这里,JVM才开始为新的Lava对象分配内存。这里刚才指向Volcano类常量池的指针就会指向Lava类常量池上,用来计算一个Lava对象需要的空间,JVM总是能够在根据方法区中一个类的信息判断其对象需要的空间

一旦创建好了对象,虚拟机就会对对象中的属性(speed)设置初始值(包括父类),之后在栈帧上压入一个该对象的引用(lava),之后的方法调用就是利用这个引用去调用初始化方法(5),之后的flow()方法调用和上面的流程类似

PC & Stack & Stack Frame

JVM是一个栈虚拟机,它并不使用寄存器保存中间值,而是使用栈帧来保存。每当创建一个新的线程,该线程就会拥有一个自己的PC计数器和Java栈,PC用来指示下一条待执行的指令

Java栈是由栈帧(Stack Frame)构成的,每当线程调用一个方法,JVM就会将一个栈帧压栈,当方法返回后,这个栈帧弹出。所有在栈里的数据都只对持有该栈的线程

jvm_5.png

栈帧由三个部分组成:

  • 本地变量
  • 操作数栈
  • 栈帧数据

本地变量包含一个方法的参数和本地变量,例如:

class Example3a {
    public static int runClassMethod(int i, long l, float f, double d, Object o, byte b) {
        return 0; 
    }
    
    public int runInstanceMethod(char c, double d, short s, boolean b) {
        return 0; 
    }
}

这段程序的栈帧中本地变量如图所示

jvm_6.png

注意下runInstanceMethod方法中第一个变量是一个reference类型,其实这就是一个this引用,对每个实例方法都会隐式地传入这个引用

最后,所有对象在Java中都是引用传递,对象本体存在于堆中,你绝不可能在本地变量,操作数栈中找到真实的对象,你只能拿到对象的引用

操作数栈也是一个栈,与本地变量采用下标访问不同,操作数栈只有出栈入栈两种操作

比方说下面几个指令:

iload_0 // push the int in local variable 0 onto the stack
iload_1 // push the int in local variable 1 onto the stack
iadd // pop two ints, add them, push result 
istore_2 // pop int, store into local variable 2

的执行过程如下图所示

jvm_7.png

栈帧数据(Frame Data)包括了对constant pool resolution,方法正常返回,异常处理的支持
JVM中有大量的指令集是和常量池相关的,凡是这样的指令虚拟机都是使用栈帧数据中的指针去获取对应的信息,之前提到过这些刚开始都是符号,当遇到符号时就会开始执行constant pool resolution

栈帧会持有对方法异常表的引用

图中同时还出现了一个本地方法栈,JVM并没有规定本地方法栈中的状态表示。但是可以看到它是独立于Java栈帧的。

所有线程共享一个堆。没有任何办法在两个JVM实例之间共享堆,下面是一个值得大家思考的问题

如何实现安卓进程间通信呢?(当然我想有很多人知道是如何实现的,不过这也是思考安卓系统架构缘由的好方式)

垃圾回收算法

现在的回收算法仍旧需要做两件事情:检测所有需要回收的对象;腾出对象空间并提供给程序使用

检测需要定义一组根节点和可达性这个概念,检测引用则有引用计数和追踪两种方式

现在的回收机制是基于Copying Collectors发展而来的Generational Collectors

Copying Collectors

将所有存活的对象移动到一个新的区域,常见的算法是”stop and copy”算法,堆会被分为两个区域。只有一块区域在特定时刻才会被使用,这块区域会一直被使用直到空间耗尽,此时程序会停止运行,然后堆进行遍历,存活对象会被拷贝到另一片区域,之后程序恢复执行。如此循环反复

Generational Collectors(当代回收算法)

简单的stop and copy算法面临的问题是所有的存活对象每次回收都需要拷贝,改进这种算法的方式基于如下两个事实:

1.绝大部分对象只有很短的生命周期
2.有些对象会有很长的生命周期

Generational Collectors将对象按照年龄分类同时相比老的对象更频繁的回收新对象,这种方式中,堆被分成两个及以上的区域,每个区域代表“一代”对象,最年轻的一代回收的最为频繁,一旦某个对象经过几次回收后仍旧没有被回收,那么它就会变老进入下一代(进入另一个堆块中),当然现在的回收算法远比这个复杂,会有更多的堆划分,更优秀的并发GC支持。比方说这样:

jvm_8.png

最后留给大家一个小问题思考下吧
为什么Android自定义View中onDraw方法不推荐创建对象呢?

最后附上例子中Volcano类和Lava类的常量池和栈帧

aLIEzTeddeMacBook-Pro:~ JianGuo$ java -version
java version "1.8.0_77"
Java(TM) SE Runtime Environment (build 1.8.0_77-b03)
Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)
aLIEzTeddeMacBook-Pro:java JianGuo$ javap -v -verbose Volcano.class
Classfile /Users/JianGuo/IdeaProjects/HelloMac/src/main/java/Volcano.class
  Last modified Dec 1, 2016; size 311 bytes
  MD5 checksum 1083bc1bf517460255a98c86e6290a69
  Compiled from "Volcano.java"
public class Volcano
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Class              #16            // Lava
   #3 = Methodref          #2.#15         // Lava."<init>":()V
   #4 = Methodref          #2.#17         // Lava.flow:()V
   #5 = Class              #18            // Volcano
   #6 = Class              #19            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Volcano.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Utf8               Lava
  #17 = NameAndType        #20:#8         // flow:()V
  #18 = Utf8               Volcano
  #19 = Utf8               java/lang/Object
  #20 = Utf8               flow
{
  public Volcano();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class Lava
         3: dup
         4: invokespecial #3                  // Method Lava."<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method Lava.flow:()V
        12: return
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 12
}
SourceFile: "Volcano.java"

aLIEzTeddeMacBook-Pro:java JianGuo$ javap -v -verbose Lava.class
Classfile /Users/JianGuo/IdeaProjects/HelloMac/src/main/java/Lava.class
  Last modified Dec 1, 2016; size 267 bytes
  MD5 checksum 83e64490eb9647ad12e2525489e5e344
  Compiled from "Lava.java"
public class Lava
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#15         // Lava.speed:I
   #3 = Class              #16            // Lava
   #4 = Class              #17            // java/lang/Object
   #5 = Utf8               speed
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               flow
  #12 = Utf8               SourceFile
  #13 = Utf8               Lava.java
  #14 = NameAndType        #7:#8          // "<init>":()V
  #15 = NameAndType        #5:#6          // speed:I
  #16 = Utf8               Lava
  #17 = Utf8               java/lang/Object
{
  public Lava();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_5
         6: putfield      #2                  // Field speed:I
         9: return
      LineNumberTable:
        line 4: 0
        line 5: 4

  void flow();
    descriptor: ()V
    flags:
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 8: 0
}
SourceFile: "Lava.java"

Reference

Inside the java virtual machine

Understanding Java Garbage Collection

How many types memory areas allocated by JVM

这是我来到简书的第一篇文章,如果有错误的话请在评论区指出,谢谢阅读!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,137评论 11 349
  • Java 虚拟机(Java virtual machine,JVM)是运行 Java 程序必不可少的机制。JVM实...
    Rick617阅读 852评论 0 0
  • JVM内存模型Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: ...
    光剑书架上的书阅读 2,467评论 2 26
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,927评论 2 31
  • 173分钟,接近三小时的时间,我选择在今晚观看完。杨德昌导演的作品《一一》,很早就列入影单,今天是看《见字如面》,...
    冬冬儿ann阅读 397评论 4 0