java 虚拟机

1、什么是类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

我们平时进行程序开发工作只是做到了第一步的java文件的编写,而后面的事情不是一般程序员所关心的事情。当我们在进行Ctrl + S操作时,我们的IDE工具(eclipse)会对我们编写的java文件进行编译工作,生成一个class文件,如果我们的程序写的没有问题,那么eclipse可以对我们的java文件进行正常的编译。

比如我们在eclipse中写下以下的java文件:


通过反编译软件(jd-gui)可以看到编译后的class文件:


但是当我们修改程序,让程序通不过编译:


F11操作,控制台输出执行结果:


而此时我们通过反编译软件(jd-gui)同样可以看到编译后的class文件中的内容:


我们可以看到反编译输出的和控制台输出的内容很类似。

当我们的java文件正常的通过编译后生成一个class文件,在程序启动运行时,进入java虚拟机模块:


1、

双亲委派
类加载器(class Loader)。

类加载器被设计成一种层级结构关系,也就是父子关系,其中bootstrap类加载器是所有类加载器的父类


--Bootstrapclass loader:

当运行java虚拟机时,这个类加载器被创建,它加载一些基本的Java API,包括Object这个类。需要注意的是,这个类加载器不是用java语言写的,而是用C/C++写的。

--Extensionclass loader:

这个加载器加载出了基本API之外的一些拓展类,包括一些与安全性能相关的类。

--SystemClass Loader:

它加载应用程序中的类,也就是在你的classpath中配置的类。

--User-DefinedClass Loader:

这是开发人员通过拓展ClassLoader类定义的自定义加载器,加载程序员定义的一些类。

委派模式(Delegation Mode)

仔细看上面的层次结构,当JVM加载一个类的时候,下层的加载器会将将任务委托给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个类,如果已经加载,直接使用这个类。如果没有加载,继续往上委托直到顶部。检查完了之后,按照相反的顺序进行加载,如果Bootstrap加载器找不到这个类,则往下委托,直到找到类文件。对于某个特定的类加载器来说,一个Java类只能被载入一次,也就是说在Java虚拟机中,类的完整标识是(classLoader,package,className)。一个类可以被不同的类加载器加载。


举个具体的例子来说明,现在加入我有一个自己定义的类MyClass需要加载,如果不指定的话,一般交App(System)加载。接到任务后,System检查自己的库里是否已经有这个类,发现没有之后委托给Extension,Extension进行同样的检查,发现还是没有继续往上委托,最顶层的Bootstrap发现自己库里也没有,于是根据它的路径(Java 核心类库,如java.lang)尝试去加载,没找到这个MaClass类,于是只好(人家看好你,交给你完成,你无能为力,只好交给别人啦)往下委托给Extension,Extension到自己的路径(JAVA_HOME/jre/lib/ext)是找,还是没找到,继续往下,此时System加载器到classpath路径寻找,找到了,于是加载到Java虚拟机。

现在假设我们将这个类放到JAVA_HOME/jre/lib/ext这个路径中去(相当于交给Extension加载器加载),按照同样的规则,最后由Extension加载器加载MyClass类,看到了吧,同一个类被两次加载到JVM,但是每次都是由不同的ClassLoader完成。

可见性限制

下层的加载器能够看到上层加载器中的类,反之则不行,也就是是说委托只能从下到上。

不允许卸载类

类加载器可以加载一个类,但是它不能卸载一个类。但是类加载器可以被删除或者被创建。

当类加载完毕之后,JVM继续按照下图完成其他工作:


框图中各个步骤简单介绍如下:

Loading:文章前面介绍的类加载,将文件系统中的Class文件载入到JVM内存(运行数据区域)

Verifying:检查载入的类文件是否符合Java规范和虚拟机规范。

Preparing:为这个类分配所需要的内存,确定这个类的属性、方法等所需的数据结构。(Prepare adata structure that assigns the memory required by classes and indicates thefields, methods, and interfaces defined in the class.)

Resolving:将该类常量池中的符号引用都改变为直接引用。(不是很理解)

Initialing:初始化类的局部变量,为静态域赋值,同时执行静态初始化块。

 

那么,ClassLoader在加载类的时候,究竟做了些什么工作呢?

要了解这其中的细节,必须得先详细介绍一下运行数据区域。

2、  运行时数据区域:

(1)      程序计数:负责分支、循环、跳转、异常处理、线程恢复

(2)      Java虚拟机栈

(3)      本地方法栈

(4) Java堆

(5)      方法区

Java虚拟机栈:Java栈也是每个线程单独拥有,线程启动时创建。这个栈中存放着一系列的栈帧(Stack Frame),JVM只能进行压入(Push)和弹出(Pop)栈帧这两种操作。每当调用一个方法时,JVM就往栈里压入一个栈帧,方法结束返回时弹出栈帧。如果方法执行时出现异常,可以调用printStackTrace等方法来查看栈的情况。栈的示意图如下:


OK。现在我们再来详细看看每一个栈帧中都放着什么东西。从示意图很容易看出,每个栈帧包含三个部分:本地变量数组,操作数栈,方法所属类的常量池引用

Java堆:几乎存放着所有的对象实例,java堆不需要在物理上连续,只要在逻辑上连续即可。

方法区:用于存储类信息、常量、静态变量、即时编译器编译后的代码、有关域和方法的信息、类和方法的字节码等数据。

运行时常量池:属于方法区的一部分,用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后进入方法取得运行时常量池中存放。

比如静态变量、实例变量、常量:静态变量存放在方法区,在类加载时进行初始化工作,并为静态变量分配内存,生命周期由该类决定;实例变量:存放于堆区,每新建一个对象,都会对该类中的实例变量分配内存,实例变量属于该对象,生命周期由对象决定;常量:存放于方法区中的运行时常量池。



对象的访问定位:

1、通过句柄访问对象:好处:引用中存放的是具体的句柄地址,在对象被移动是只会改变句柄中的实例数据指针 ,而引用本身不需要修改;

2、通过指针进行对象访问:节省了一次指针定位的时间开销,速度更加快。

1、通过句柄访问对象:


2、通过指针访问对象:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值