A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© Quasimodo2 中级黑马   /  2016-10-16 22:21  /  1449 人查看  /  8 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

jvm已经是Java开发的必备技能了,jvm相当于Java的操作系统。JVM,java virtual machine, 即Java虚拟机,是运行Java Class文件的程序。

Java代码经过Java编译器编译,会编译成class文件,一种平台无关的代码格式,class文件按照jvm规范,包括了java代码运行所需的元数据和代码等内容。jvm加载class文件后,就可以执行java代码了。

JVM有不同的实现,有我们熟悉的Hotspot虚拟机,JRockit等。在各个操作系统上,又回有各自的虚拟机实现,从而形成了Java代码 > class文件 > JVM规范 > JVM实现的层次。再加上其他语言如scala、groovy也能够生成class文件,这样不仅实现了平台无关性,也实现了语言无关性。

JVM体系,分为JVM内存结构,Class文件结构,Java ByteCode,垃圾收集算法和实现,调优和监控工具,以及Java内存模型(JMM)。



JVM内存结构



通常,认为大概分为线程共享的区域和线程私有的区域。共享区域在JVM启动时创建,私有区域伴随这线程的启动和结束。

私有区域

一个线程拥有的结构有

程序计数器(Program Counter, PC)

Java天生支持多线程,多线程会有线程切换的问题,当一个线程从可运行状态得到CPU调度进入运行状态,CPU需要知道从哪里开始执行,并且Java是一种基于栈的执行架构(区别于基于寄存器的架构)。当执行一个Java方法时,PC会指向下一条指令的位置。执行native方法时,PC是未定义。操作指令可能会有0个或多个操作数。JVM的执行流程大概可以描述为:

while(true) {
opcode = code[pc];
oprand = getOperan(opcode);
pc = code[pc + len(oprand)];
execute(opcode, oprand);
}
Java虚拟机栈(Java Virtual Machine Stack)

Java虚拟机栈,或者叫方法栈,会伴随这方法的调用和返回进行相应的入栈和出栈。栈的元素是栈帧(Stack Frame), 栈帧中的内容包括: 操作数栈,本地变量表,动态链接等信息。当线程调用一个方法的时候,会组装对应的栈帧入栈。

本地变量表(Local Variable Table)

本地变量表存储方法的参数、方法内部创建的局部变量。本地变量表的大小在编译时就确定了。本地变量表会根据变量的作用范围选择重用一个位置。本地变量表会存放int,char,byte,float,double,long,address(实例引用)。其中除了double和long其他变量占用一个slot,一个slot指一个抽象的位置,在32位虚拟机中是32bit大小,double和long占用两个slot。

值得注意的是,如果一个方法是实例方法,Java编译器会将this作为第一个参数传入本地变量表。另外Java中面向对象,方法调用可以这样理解

实例方法
obj.method(var1, var2, var3) => method invoke obj var1 var2 var3
操作数栈

操作数栈用于方法内执行保存中间结果,Java方法中的代码逻辑就是通过操作数栈来实现的。和本地方法表一样,操作数栈也是在编译时就确定最大大小了,即最大深度。操作数栈可以和本地变量表交互,进行数据的存放和读取。下面用一个简单的例子展示一下。

int add(int a, int b) {
return a + b;
}
这个实例方法经过Java编译器编译后生成的字节码

本地变量表
slot0 this
slot1 a
slot2 b

方法字节码
iload_1 #读位置是1的本地变量(本地变量表从0开始,位置0是this引用)
此时操作数栈是 a
iload_2 #读位置是2的本地变量,即b
此时操作数栈是 a b
iadd #进行int类型的add操作,会取出栈头的两个元素取出进行相加并将结果入栈。
此时操作数栈是 c (相加的结果)
ireturn #ireturn指令会将栈头元素返回给调用方法的栈帧
线程共享区域

堆(Heap区)

创建的对象(包括普通实例和数组)都分配在Heap区(不考虑一些虚拟机的栈上分配优化技术)。在细分的话,一般还分成年轻代和老年代。这是基于这样一个类似28原理的统计,90%多的对象都是很快成为垃圾的对象。所以化为成两个区域,分别使用不同的收集算法,年轻代的收集频率更高,所需空间也相对较小。内存分配时,多个线程会有并发问题,主要通过两种方式解决:1.CAS加上失败重试分配内存地址。2. TLAB, 即Thread Local Allocation Buffer, 为每个线程分配一块缓冲区域进行对象分配。年轻代还可以分为两个大小相等的Survivor和一个Eden区域。对象在几种情况下会进入老年代:1. 大对象,超过Eden大小或者PretenureSizeThreshold. 2. 在年轻代的年龄(经历的GC次数)超过设定的值的时候 3. To Survivor存放不下的对象

方法区

方法区存放加载的类信息和运行时常量池等。

垃圾收集(Garbage Collect)

Java中不需要对内存进行手动释放,JVM中的垃圾回收器帮助我们回收内存。

何时进行收集

一般来说,当某个区域内存不够的时候就会进行垃圾收集。如当Eden区域分配不下对象时,就会进行年轻代的收集。还有其他的情况,如使用CMS收集器时配置CMSInitalize

如何判断一块内存是垃圾

即判断一个对象不再使用,不再使用可以是没有有效的引用。

一般来说,主要有两种判断方式

引用计数

当有对象引用自身时,就会计数器加1,删除一个引用就减一,当计数为0时即可判断为垃圾。python等语言使用引用计数。引用计数存在循环引用问题,如两个落单的A和B互相引用,但是没有其他对象指向它们这种情况。

可达性分析

通过一些根节点开始,分析引用链,没有被引用的对象都可以被标记为垃圾对象。根节点是方法栈中的引用、常量等。

垃圾收集算法

标记清除(Mark Sweep)

对非垃圾对象进行标记都,清除其他的对象。这种方式对对内存空间造成空隙,即内存碎片,最终导致有空余空间,但没有连续的足够大小的空间分配内存。

标记整理(Mark Compact)

标记非垃圾对象后,将这些对象整理好,排列到内存的开始位置。这样内存就是整齐的了。但是因为会造成对象移动,所以效率会有降低。

标记清除整理(Mark Sweep Compact)

即组合两种方式,在若干次清除后进行一次整理。

复制(Copy)

划分成两个相同大小的区域,收集时,将第一个区域的活对象复制到另一个区域,这样不会有内存碎片问题。但是最多只能存放一半内存。

垃圾收集器

垃圾收集器就是垃圾收集算法的相应实现。

Serial New

新生代单线程的收集器,是Client模式默认的垃圾收集器

Parallel New

Serial New的多线程版本。ParNew常和CMS拉配使用。这里说明一些Parallel和Concurrent即并行和并发在垃圾收集这里的表示的不同,并行表示有多个线程同时进行垃圾收集,并发是指垃圾收集线程和应用线程可以并发执行。

评分

参与人数 1黑马币 +2 收起 理由
wgc + 2

查看全部评分

来自宇宙超级黑马专属苹果客户端来自宇宙超级黑马专属苹果客户端

8 个回复

倒序浏览
回复 使用道具 举报
回复 使用道具 举报
wgc 中级黑马 2016-10-17 00:25:51
板凳
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
楼主总结的很好,学习了
来自宇宙超级黑马专属苹果客户端来自宇宙超级黑马专属苹果客户端
回复 使用道具 举报
好详细的说
回复 使用道具 举报
冷猫 中级黑马 2016-10-17 10:55:57
9#
好长啊.. ..
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马