JVM重温

来源:互联网 发布:橙光游戏制作工具mac 编辑:程序博客网 时间:2024/06/05 05:26

最近又把JVM简单的回看了一下,发现很多东西理解起来还是有点难度的,也不是说自己理解不了、能力不行或者怎样,而是这个JVM确实包含了太多东西,要一一都搞清楚的话需要下很大的功夫,而我又不是一个专业来搞这方面的,因此看的时候就为了写代码时能有所提高或者避免一些坑或者在出问题时能简要分析一下,因此看的就不可能深入了。



主要是JVM的内存管理、GC原理、class的结构、class的执行


其实光是JVM的概念理解起来就挺麻烦的,JDK的源码与JDK的关系,JVM的源码与JVM的关系,姑且将其想象成我们口中一贯呼之的JVM吧,不去深究,就当做是个工具。


JVM将其管理的内存分成几个部分:PCR(程序计数器)、VM Stack(虚拟机栈)、Native Stack(本地方法栈)、VM Heap(JVM的堆)、方法区。还有个直接内存。


1、PCR
PCR是线程私有的,每个线程都有一个PCR,用来记住当前线程执行到哪儿了,因为现在的程序都是多线程的,而每个线程执行时都占用一个CPU,但是呢CPU又是有限的,所以当多个线程切换执行时,为了记住该线程执行到哪儿了,需要保存一个指向当前线程执行到的字节码的指针。(书上的解释也到位:可以看做是当前线程所执行的字节码的行号指示器,如果执行的是Native方法,则这个计数器的值为空Undefined)


2、Virtual Machine Stack
即通常我们口头上所说的栈,这个栈是用来具体执行方法的,每调用一个方法,就将这个方法所对应的栈帧入栈,执行完之后这个栈帧就出栈,返回到调用方,即上一个栈帧,这都是在VM Stack中进行的。栈的生命周期和线程的生命周期一致,创建线程时就有了栈(有没有可能创建了线程却没有或暂时没有分配栈的内存空间?),线程执行完后栈就被销毁了。栈的深度是指栈中可以入栈的栈帧数量,每个栈帧大小是不一样的,因为每个方法的具体代码不一样。一般的栈的深度2000就差不多了(一般方法调用链是没有2000的,但是有些递归是可以达到的),当栈内空间满了后还有栈帧入栈的话,就会爆StackOverflowError异常(前提是这个栈不能动态扩展),一般栈是可以动态扩展的,但是如果动态扩展时申请不到足够的内存,则会报OutOfMemoryError异常。


3、Native Stack
本地方法栈用来执行本地方法,也和栈一样的生命周期吗?


4、VM Heap
通常口头而言“堆”,用来存储对象和数组的(注意:数组是一个特殊的对象),应该是JVM内存中最大的一片区域了吧。这个是线程共享的。现在一般把堆分为了新生代和老年代,新生代用来存放新创建的对象,老年代用来存放经过几次GC之后都没有死掉的对象。(这样解释多简单啊^_^)堆的大小可以由参数控制,也可以扩展,和栈一样的也会抛出OutOfMemoryError异常。


5、方法区
用来存放class文件的结构(也有的JVM存Class对象)、运行时常量、静态变量等信息。


6、直接内存Direct Memory

JVM管理的内存不够大,所以就来了个直接内存,通过DirectByteBuffer对象来操作它,这部分内存的大小不受限,但是所有内存总和加起来不得超过物理内存。


对象的内存布局:

分为3块:对象头、实例数据、对齐填充。

对象头中的数据又分为两部分:一是对象自身的运行时数据,而是类型指针(指向该对象的类元数据的指针)。

实例数据:对象自己的和从父类几个的各种类型的字段。

对齐填充没什么实际意义。

这里引用一副网上的图片好了:



对象的访问:两种方式:直接指针访问、句柄访问

上图最清晰:


直接访问如下:

  

句柄访问如下:

 



垃圾收集的原理


垃圾回收是指将无效对象所占用的空间释放掉,以便再次使用。而怎么判断一个对象是否有效呢,有两种方法:
1、引用计数法:对对象的引用计数,每当该对象被引用时将计数器加1,不引用后减1,那么当计数器为0时,则可知该对象没有被任何地方引用了则可以将其毁灭了。这种方式可能有循环引用的情况,不太好,所以没有使用了。
2、可达性分析法:从一系列的起点对象(GC ROOTS)开始向下搜索,那么这个搜索链上的所有对象都是有用的,如果一个对象没有被任何引用链相连,那么这个对象就是没用的。
GC ROOTS对象包括:
1、虚拟机栈中的对象(栈帧中的本地变量表);
2、本地方法栈中Native方法(JNI)引用的对象;
3、方法区的类静态属性引用的对象;
4、方法区中常量引用的对象。



回收算法:
1、标记-清理法
2、标记-整理-压缩法
3、复制法:Eden、From Survivor、To Survivor记得这三个区域8:1:1就知道了。
4、分代收集法:这个中和了上面三个算法,也是现代JVM采用的。

HotSpot(JDK1.7u14)的垃圾收集器:
新生代中的有:Serial、ParNew、Parallel
老年代中的有:Serial Old、Parallel Old、CMS
G1在新生代和老年代中都使用到了

收集器没有最好,都是搭配使用的
各个收集器的优缺点就不写了,网上有个列表挺清晰的。


Class文件结构
两种数据类型:无符号数和表;



CAFEBABE 版本号 常量池……

java文件的编译与运行与JDK的版本号有关,高版本的JDK可以兼容低版本的class文件,就是靠判断class文件中的主版本号来的。



Class的加载

过程:加载—验证—准备—解析—初始化—执行—卸载
加载:class文件(二进制字节流)被类加载器加载到内存中,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,在内存中生成一个代表这个类的java.lang.Class对象,这个对象可能在堆中,也可以在方法区中(HotSpot是放在方法区中的)。
验证:验证是否为class文件(CAFEBABE),验证版本号,验证这个类是否有未实现的方法,重载是否正确,是否覆盖了final方法……验证数据类型、常量池……
准备:为类变量(static)分配内存并设置初始值(0,false,'',null等),类变量都是分配在方法区中的。
解析:将常量池中的符号引用替换成直接饮用。
初始化:执行类构造器<clinit()>,给类变量赋值并且执行static代码块





1 0
原创粉丝点击