JVM-漫游
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-漫游的更多相关文章
- 46张PPT讲述JVM体系结构、GC算法和调优
本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述.(内嵌iframe,建议使用电脑浏览) 好东西当然要分享,PPT已上传可供下载 ...
- java 利用ManagementFactory获取jvm,os的一些信息--转
原文地址:http://blog.csdn.net/dream_broken/article/details/49759043 想了解下某个Java项目的运行时jvm的情况,可以使用一些监控工具,比如 ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- JVM类加载
JVM的类加载机制就是:JVM把描述类的class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被JVM直接使用的Java类型 ClassLoader JVM中的ClassLoade ...
- Java虚拟机 JVM
finalize();(不建议使用,代价高,不确定性大) 如果你在一个类中覆写了finalize()方法, 那么你可以在第一次被GC的时候,挽救一个你想挽救的对象,让其不被回收,但只能挽救一次. GC ...
- 在 Linux 中安装 Oracle JDK 8 以及 JVM 的类加载机制
参考资料 该文中的内容来源于 Oracle 的官方文档 Java SE Tools Reference .Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以直接找 ...
- MapReduce剖析笔记之六:TaskTracker初始化任务并启动JVM过程
在上面一节我们分析了JobTracker调用JobQueueTaskScheduler进行任务分配,JobQueueTaskScheduler又调用JobInProgress按照一定顺序查找任务的流程 ...
- java太low,又舍不得jvm平台的丰富资源?试试kotlin吧(一)
尝试kotlin的起因 因为各种原因(版权,人员招聘),公司的技术体系从c#转到了java,我花了大概两周的时间来上手java,发现java的语法还是非常简单的,基本看着代码就知道什么意思.学习jav ...
- Jvm --- 常用工具
jps:虚拟机进程状况工具 JVM Process Status Tool. 可以列出所有目前正在运行虚拟机的进程. jps -l 详细参数: -q 输出LVMID,省略主类名称 -m 输出虚拟机进程 ...
- JVM虚拟机结构
JVM的主要结构如下图所示,图片引用自舒の随想日记. 方法区和堆由所有线程共享,其他区域都是线程私有的 程序计数器(Program Counter Register) 类似于PC寄存器,是一块较小的内 ...
随机推荐
- 2014年百度之星程序设计大赛 - 初赛(第二轮)JZP Set
题目描述:一个{1, ..., n}的子集S被称为JZP集,当且仅当对于任意S中的两个数x,y,若(x+y)/2为整数,那么(x+y)/2也属于S.例如,n=3,S={1,3}不是JZP集,因为(1+ ...
- 【记录】EF Code First 实体关联,如何添加、修改实体?
在使用 EF Code First 的时候,我们经常会对项目中的 Entry 进行一对多.多对多的映射配置,这时候就会产生主实体和子实体的概念,我们在添加.修改他们的时候,有时候会产生一些问题,比如添 ...
- make things simple
以前看过一篇文章,具体内容不记得了,只记得它的结论了:懒是人类进步的源动力.当时觉得结论有点新颖,文中列举了大量的实例证明这个结论,其中重点强调了计算机学科.我本身从事算是计算机相关的工作,对文中的部 ...
- 用PHP抓取页面并分析
在做抓取前,记得把php.ini中的max_execution_time设置的大点,不然会报错的.
- c++编译器对多态的实现原理总结
问题:定义一个空的类型,里面没有任何的成员变量或者成员函数,对这个类型进行 sizeof 运算,结果是? 结果是1,因为空类型的实例不包含任何信息,按道理 sizeof 计算之后结果是0,但是在声明任 ...
- Oracle监控用户索引使用情况,删除无用索引
监控当前业务用户索引 一段时间后查询从未被使用的索引,删除无用索引 停止监控索引 1. 监控当前用户所有索引 得到监控所有索引的语句: select 'alter index ' || index_n ...
- Oracle配置和使用闪回
环境:RHEL 6.4 + Oracle 11.2.0.4 目录: 一.闪回查询 1.1 闪回查询举例 1.2 闪回版本查询举例 二.闪回事物 2.1 闪回事物查询的先决条件 2.2 闪回事物查询 三 ...
- 【集合框架】JDK1.8源码分析之HashMap & LinkedHashMap迭代器(三)
一.前言 在遍历HashMap与LinkedHashMap时,我们通常都会使用到迭代器,而HashMap的迭代器与LinkedHashMap迭代器是如何工作的呢?下面我们来一起分析分析. 二.迭代器继 ...
- 【原创】新手入门一篇就够:从零开发移动端IM
一.前言 IM发展至今,已是非常重要的互联网应用形态之一,尤其移动互联网时代,它正以无与论比的优势降低了沟通成本和沟通代价,对各种应用形态产生了深远影响. 做为IM开发者或即将成为IM开发者的技术人员 ...
- Extjs4.0以上版本 Ext.Ajax.request请求的返回问题
Ext.Ajax.request({ url: posturl, method: 'POST', params: { ClassName: 'XXXX', FuncName: 'XXXX', para ...