Write once, Run Any where. Java Virtual Machine – JVM 的存在让 Java 开发变得简单,并且一次编写多处运行。其实,JVM 就是一个抽象的计算机,它有自己的指令集,有自己的机器语言,有自己的内存管理,只要能生成规范的字节码文件,它都能运行。本文将站在 Java程序员 的角度,带着一个简单的 Java 代码来一次漫游历程,了解一下这些 Java代码 背后的故事。

  本文基于 Java HotSpot™ 虚拟机,JDK 8,从整体上进行描述。为了方便描述,将会忽略部分细节。更多详细信息请查看以下内容,传送门:

1. 概述

  首先,编写一个 Hello World!

public class Hello {
	public static void main(String[] args) {
        Hello hello = new Hello();
		System.out.println("Hello World! " + hello);
	}
}
00000000 ca fe ba be 00 00 00 34 00 1e 0a 00 06 00 10 09
00000010 00 11 00 12 08 00 13 0a 00 14 00 15 07 00 16 07
00000020 00 17 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29
00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e
00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 04 6d 61 69
00000050 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67
00000060 2f 53 74 72 69 6e 67 3b 29 56 01 00 0d 53 74 61
00000070 63 6b 4d 61 70 54 61 62 6c 65 01 00 0a 53 6f 75
00000080 72 63 65 46 69 6c 65 01 00 0a 48 65 6c 6c 6f 2e
...

  使用 javac 将源代码编译成 JVM 能识别的字节码 - Class 文件(如上二进制),Class 文件描述了一个类或接口的字段,方法,父类,常量池等信息,当我们使用 java 命令去执行一个 class文件或者jar 文件时,就启动了一个 JVM 进程。下面是 java 常用的参数和用法:

java [-options] class [args...]
java [-options] -jar jarfile [args...]
options:
  -classpath, -cp : 指定类文件搜索路径
  -D<name>=<value> : 设置一个系统属性
  -jar : 执行jar包中的应用程序
  -verbose, -verbose:class : 显示类加载信息
  -verbose:gc : 显示每个垃圾回收事件
  -verbose:jni : 显示调用本地方法或接口信息

2. VM Lifecycle

(1)使用 Java 启动 JVM,解析命令行选项,如 -client 或者 -server,加载相应的 VM 库

(2)确定堆大小,编译器类型(client or server);确定环境变量(CLASSPATH)

(3)获取 Main-Class 的类名

(4)新建主线程,调用 JNI_CreateJavaVM 创建 VM,并初始化

(5)从 Main-Class 中获取 main 方法,使用 CallStaticVoidMethod 调用main方法

(6)方法结束,检查并清除可能发生的异常,回调 ExceptionOccurred

(7)主线程使用 DetachCurrentThread 退出,减小线程计数,调用 DestroyJavaVM 退出 JVM

  以上就是 JVM 的生命周期,下面着重看一下 JVM的创建,初始化和 JVM的退出。

2.1 JNI_CreateJavaVM

(1)确保只有一个线程调用此方法

(2)检查 JNI 版本,初始化GC日志记录,初始化 OS 模块(如随机数生成器,当前pid,内存页大小,保护页等)

(3)解析传递的命令行参数,初始化标准系统属性

(4)根据解析的参数和属性,进一步初始化 OS模块,堆栈,内存和 safepoint 页。加载VM库如 libzip,libjava。初始化线程库

(5)初始化输出流记录器,初始化一些特定的线程

(6)初始化全局数据,如事件日志,内存分配器,同步原语

(7)创建线程,并把main主线程映射到当前操作系统线程,此时线程还没有添加到线程队列。初始化Java级别的同步

(8)初始化其他模块,如BootClassLoader,Interpreter,Compiler,JNI,SystemDictionary(系统字典)

(9)添加主线程到线程列表,创建VMThread(JVM内部线程,执行一些关键功能)

(10)加载并初始化 Java 核心类库

(11)启动其他辅助线程,并把 JNIEnv 返回给调用者,等待相应其他 JNI 调用

2.2 DestroyJavaVM

(1)等待所有的非守护线程结束

(2)调用 java.lang.Shutdown.shutdown(),若finalization-on-exit为true,调用对象的finalizer

(3)调用 before_exit(),关闭VM级别钩子,停止垃圾收集线程,性能分析线程等

(4)调用 JavaThread::exit(),释放 JNI处理块,移除保护页,移除当前线程

(5)停止 VM线程,停止追踪JNI,将本地运行的线程标记为vm exited,删除当前线程

(6)释放资源,返回调用者

3. Hello World

  接下来让我们深入 JVM 内部探究一下这个Hello类,使用到的工具有:jdb(一个命令行调试器,eclipse太大,还占内存OoO)和 HSDB

  首先启动 jdb,并把断点设置到main函数,这里指定使用 SerialGC 和 10m 的堆。

F:\jvm>jdb -XX:+UseSerialGC -Xms10m
正在初始化jdb...
> stop in Hello.main
正在延迟断点Hello.main。
将在加载类后设置。
> run Hello
运行 Hello
设置未捕获的java.lang.Throwable
设置延迟的未捕获的java.lang.Throwable
>
VM 已启动: 设置延迟的断点Hello.main

断点命中: "线程=main", Hello.main(), 行=9 bci=0
9            Hello hello = new Hello();

main[1] next
>
已完成的步骤: "线程=main", Hello.main(), 行=10 bci=8
10              System.out.println("Hello World! " + hello);

main[1] locals
方法参数:
args = instance of java.lang.String[0] (id=405)
本地变量:
hello = instance of Hello(id=406)
main[1]

  使用 jps 查询进程id,并用 HSDB(或CLHSDB) 连接到此进程。此时 new 了一个Hello对象,可以知道,它首先在 eden区分配,那么让我们验证一下。

  首先,查看一下堆各个代的地址空间(可以看到在JDK 8 中,已经没有PermGen区域了),然后在 eden 区扫描一下看看是否有 Hello 的实例,结果显示有一个,查看这个地址的内容,表示这个对象在main 线程的 TLABs 中,接下来又使用 inspect 命令查看了这个对象的具体内容。使用图形界面看一下这个对象的内容,具体信息如下:

  字段的意义如下:

  • _mark:Mark Word
  • _klass:元数据指针
  • _java_mirror:instanceKlass 镜像类,在Java级别访问元信息,就是 java.lang.Class 类,应该是为了安全,包装了一层
  • _super:父类
  • _layout_helper:正数表示此实例大小,负数表示数组
  • _access_flags:访问控制
  • _subklass:子类
  • _next_sibling下一个兄弟结点
  • _array_klasses:数组类型信息
  • _nonstatic_field_size:非静态字段域偏移量
  • _static_field_size:静态字段域偏移量
  • _static_oop_field_count:静态oop字段
  • _nonstatic_oop_map_size:实例oop域
  • _is_marked_dependent:是否依赖(?)
  • _init_state:类状态
  • _vtable:内嵌虚方法表
  • _itable:内嵌接口方法表

最后在看一下线程内存信息(地址是向上增长的):

  • expression stack:这个是操作数栈
  • frame:这部分表示栈帧
  • locals area:表示局部变量区域
  • New Hello:第 1 个局部变量,hello
  • NewGen objArray:第 0 个局部变量,字符串数组参数

  这里只是 JVM 里的一小部分信息,由于这个 Hello 比较简单,接下来会分析一个常用的类-java.lang.String 在 JVM 内部的信息。

JVM-漫游的更多相关文章

  1. 46张PPT讲述JVM体系结构、GC算法和调优

    本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述.(内嵌iframe,建议使用电脑浏览) 好东西当然要分享,PPT已上传可供下载 ...

  2. java 利用ManagementFactory获取jvm,os的一些信息--转

    原文地址:http://blog.csdn.net/dream_broken/article/details/49759043 想了解下某个Java项目的运行时jvm的情况,可以使用一些监控工具,比如 ...

  3. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  4. JVM类加载

    JVM的类加载机制就是:JVM把描述类的class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被JVM直接使用的Java类型 ClassLoader JVM中的ClassLoade ...

  5. Java虚拟机 JVM

    finalize();(不建议使用,代价高,不确定性大) 如果你在一个类中覆写了finalize()方法, 那么你可以在第一次被GC的时候,挽救一个你想挽救的对象,让其不被回收,但只能挽救一次. GC ...

  6. 在 Linux 中安装 Oracle JDK 8 以及 JVM 的类加载机制

    参考资料 该文中的内容来源于 Oracle 的官方文档 Java SE Tools Reference .Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以直接找 ...

  7. MapReduce剖析笔记之六:TaskTracker初始化任务并启动JVM过程

    在上面一节我们分析了JobTracker调用JobQueueTaskScheduler进行任务分配,JobQueueTaskScheduler又调用JobInProgress按照一定顺序查找任务的流程 ...

  8. java太low,又舍不得jvm平台的丰富资源?试试kotlin吧(一)

    尝试kotlin的起因 因为各种原因(版权,人员招聘),公司的技术体系从c#转到了java,我花了大概两周的时间来上手java,发现java的语法还是非常简单的,基本看着代码就知道什么意思.学习jav ...

  9. Jvm --- 常用工具

    jps:虚拟机进程状况工具 JVM Process Status Tool. 可以列出所有目前正在运行虚拟机的进程. jps -l 详细参数: -q 输出LVMID,省略主类名称 -m 输出虚拟机进程 ...

  10. JVM虚拟机结构

    JVM的主要结构如下图所示,图片引用自舒の随想日记. 方法区和堆由所有线程共享,其他区域都是线程私有的 程序计数器(Program Counter Register) 类似于PC寄存器,是一块较小的内 ...

随机推荐

  1. Python学习第一弹——Python环境搭建

    一.Python简介: Python,是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年.Python语法简洁而清晰,具有 ...

  2. geotrellis使用初探

    最近,单位领导要求我研究一下geotrellis(GITHUB地址:https://github.com/geotrellis/geotrellis,官网http://geotrellis.io/), ...

  3. Windows Phone 8 开发系列(持续更新中)

    1. 从应用列表再次点击应用,如何恢复到上次浏览的页面呢? 2. Windows Phone 文本框的 UpdateSourceTrigger 属性不支持 PropertyChanged 怎么办? 3 ...

  4. Moon.Orm版本维护及下载(跟踪报道)

    提示:最下面有最新的下载地址  历史记录: ).-- :: 支持Mysql,Sqlserver(点击下载) ).2013年9月14日,代码重构,加入oracle.复合主键功能(点击下载) )--,为d ...

  5. C#无限极分类树-创建-排序-读取 用Asp.Net Core+EF实现之方法二:加入缓存机制

    在上一篇文章中我用递归方法实现了管理菜单,在上一节我也提到要考虑用缓存,也算是学习一下.Net Core的缓存机制. 关于.Net Core的缓存,官方有三种实现: 1.In Memory Cachi ...

  6. 原生JS投票特效

    效果:http://hovertree.com/texiao/js/24/ 效果图: 代码如下: <!DOCTYPE html> <html lang="en"& ...

  7. jquery——左右按钮点击切换一组图片功能

    一.最终效果 二.功能分析 1.需求分析 点击左边pre按钮,显示前面三个图片,点击右边的next按钮,显示后面的一组(三个)图片.初始化只显示next按钮,到最后一组只显示pre按钮,中间过程两按钮 ...

  8. html节点属性操作

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  9. IDCM项目学习笔记

    项目介绍: IDCM:Internet Data center monitoring 网络数据中心监控平台 IRP:Information Resource planing 信息资源规划 1.设置表中 ...

  10. [moka同学笔记]七、Yii2.0课程笔记(魏曦老师教程)[新增管理员,重置密码]