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. IDDD 实现领域驱动设计-架构之经典分层

    上一篇:<IDDD 实现领域驱动设计-上下文映射图及其相关概念> 在<实现领域驱动设计>书中,分层的概念作者讲述的很少,也就几页的内容,但对于我来说,有很多的感触需要诉说.之前 ...

  2. 学习SpringMVC——如何获取请求参数

    @RequestParam,你一定见过:@PathVariable,你肯定也知道:@QueryParam,你怎么会不晓得?!还有你熟悉的他(@CookieValue)!她(@ModelAndView) ...

  3. WebApi Controller 分类

    前言 写这篇WebApi Controller分类一是把Contrller分类的解决方法说一说,再顺便把遇到的问题和解决方法聊一聊. 说实话第一次在项目中使用WebApi感觉非常的不顺手. 遇到的问题 ...

  4. Go语言异常处理defer\panic\recover

    Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱.因为开发者很容易滥用异常, ...

  5. CloudNotes云端个人笔记系统系列文章汇总

    [CloudNotes版本更新信息与下载地址:http://cloudnotes.cloudapp.net/webapi/Home/Release] [CloudNotes RESTful API帮助 ...

  6. SQL Server 存储(1/8):理解数据页结构

    我们都很清楚SQL Server用8KB 的页来存储数据,并且在SQL Server里磁盘 I/O 操作在页级执行.也就是说,SQL Server 读取或写入所有数据页.页有不同的类型,像数据页,GA ...

  7. [Web API] Web API 2 深入系列(6) Model绑定(上)

    目录 解决什么问题 Model元数据解析 复杂类型 ValueProvider ValueProviderFactory 解决什么问题 Model: Action方法上的参数 Model绑定: 对Ac ...

  8. C# 管理员身份运行程序

    在使用winform程序获取调用cmd命令提示符时,如果是win7以上的操作系统,会需要必须以管理员身份运行才会执行成功,否则无效果或提示错误. 比如在通过winform程序执行cmd命令时,某些情况 ...

  9. mvc实现上传图片(上传和预览)webuploader

    笔者看到mvc最近比较流行,而很多使用一些比较旧的的方法上传图片,再次安利一下百度的webuploader控件吧 webuploader第一步要先下载一些插件这点可以在webuploader官网上下载 ...

  10. 背水一战 Windows 10 (17) - 动画: ThemeTransition(过渡效果)

    [源码下载] 背水一战 Windows 10 (17) - 动画: ThemeTransition(过渡效果) 作者:webabcd 介绍背水一战 Windows 10 之 动画 ThemeTrans ...