博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java基础知识
阅读量:3889 次
发布时间:2019-05-23

本文共 5703 字,大约阅读时间需要 19 分钟。

类加载

JVM把描述类的数据从class文件加载进内存,并对数据进行校验,转换解析和初始化,最终形成可被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

任意一个类都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性
讲一讲类加载器工作机制?什么时候需要自定义类加载器
答:①类的加载是指将类的.class文件中的二进制数据读到内存中,将其放到运行时数据区的方法区内,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构
②加密时需要自定义类加载器:加密:众所周知,java代码很容易被反编译,如果你需要把自己的代码进行加密,可以先将编译后的代码用某种加密算法加密,然后实现自己的类加载器,负责将这段加密后的代码还原(如公司把核心类库的字节码给加密了,这样加载类的时候就必须对字节码进行解密,可以通过findClass读取URL中的字节码,然后加密,最后把字节数组交给defineClass()加载)
类似的还有从非标准的来源加载代码:例如你的部分字节码是放在数据库中甚至是网络上的,就可以自己写个类加载器,从指定的来源加载类。
动态创建:为了性能等可能的理由,根据实际情况动态创建代码并执行
还有隔离,热部署等场景可能需要自定义类加载器

使用类加载器有一些注意点自定义加载器加载的类不能是java/javax和其子包的类

使用defineClass方法定义的多个类,即使字节码完全相同,它们也不是同一个类(Class对象)
要在java代码中调用加载的类的实例通常需要使用反射,但是如果用于加载这个类的类加载器在加载这个类时,是从systemClassLoader获取的接口或父类,那么就可以在源代码中使用如下方式调用方法
Super o=(Super)classLoader.loadClass(“xx.xx.C”).newInstance();
o.method();
什么时候会启动类加载器?
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料到某个类即将被使用时就预先加载它,如果预加载时遇到了class文件不存在或缺失等问题,类加载器此时不会报错,必须等到程序主动使用该类时才报告错误(LinkageError错误)
从哪里可以加载.class文件
(1)本地磁盘
(2)网上加载.class文件(Applet)
(3)从数据库中
(4)压缩文件中(ZAR,jar等)
(5)从其他文件生成的(JSP应用)

那么什么时候需要这个类呢,以下几种常见情况:

new一个对象,或者调用一个类的静态字段或者静态方法(getstat)
反射调用一个类
子类加载前要先加载父类
虚拟机刚启动时执行主类
这些情况,都是属于对类的主动引用。

类加载(classLoader)采用双亲委托模型,实现自定义加载器时一般不覆盖loadClass函数,而是继承父加载器不能完成加载任务时调用的findClass(name)函数,

类加载过程?

分为五步:加载、验证、准备、解析和初始化,其中验证、准备和解析合并称为链接
所以类加载的过程就是 加载 链接 和初始化

Java实现平台无关性的基石就是字节码。在Java虚拟机中,有一个class文件这个概念。一般情况下,每一个类都会产生一个class文件,其内容就是字节码。虚拟机执行字节码,其实就是加载了类的class文件。

Class文件结构

任何一个class文件都对应着唯一一个类或者接口的定义信息。但是类或者接口又不必一定非要在class文件中(比如动态的通过类加载器加载)。class文件是一组二进制流,其中包含和类相关的所有信息,非常紧凑的排列在一起,很严格的规定了第几位到第几位是什么,主要包含了魔数,常量池等数据信息。

这部分内容看起来还是很无聊的,主要关注其中一部分就好。比如一开头的4个字节是魔数,魔数的唯一作用是确定这个文件是否可以被虚拟机接受。

类加载机制

类的生命周期分为7个阶段:
加载 验证 准备 解析 初始化 使用 卸载,其中,验证 准备 解析三个步骤又可以合并为链接
加载的时机——按需加载
虚拟机并没有规定类的加载过程什么时候开始,只是明确了类加载的生命周期是固定的。但是比较特别的是“初始化”。我们需要用到一个类的时候,就一定要“初始化”,而其他在他之前的步骤,自然也就必须要调用了。因此可以这样概括为:加载、验证、准备、解析,这个过程是不确定的,由不同虚拟机自己控制,可能不知道哪个时候就进行了。但是当我们需要用到一个类时,就必须要立刻从加载开始执行到初始化结束,之后才能使用。

加载的过程——五步走

前面说过了,类的加载过程是类的生命周期前五个步骤:

加载:

通过一个类的全限定名来获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的class对象,作为方法区这个类的各种数据访问入口
因为加载这个过程没有限制具体的来源,所以衍生出了很多新东西,比如Jar包的读取,从网络中加载类等。

这是对于简单类而言的。对于数组,不会通过类加载器加载,而是由虚拟机直接创建,之后才会递归的加载数组中的引用类。

验证:验证是链接过程的第一步,目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,且不会危害虚拟机本身的安全。验证主要有四类:

文件格式验证:字节流是否符合Class文件格式规范
元数据验证:语义分析,符合语言规范
字节码验证:分析数据流,确定语义是合法的,符合逻辑的。
符号引用验证:验证符号引用合法性

准备:正式为类量分配内存并设置初值。类变量要分配在方法去中,设置初值的是类变量而不是实例变量。

解析:将常量池内的符号引用替换为直接引用。前面说过,符号引用只是以简单的通过名称等信息指出引用的方法或类,。那么在这里才会真正的将符号引用转换为直接引用,即对于方法区类的引用。直接引用类似于指针,所以这一过程可以理解为从名称到地址的转化。
初始化:前面是加载和链接的过程,这里就是类加载过程的最后一步了。所谓的初始化阶段,就是真正执行在类中写的代码了。比如实例变量的初始化和构造器等。初始化阶段也可以理解为调用类的构造器的过程。
加载的工具——类加载器
前面说过,第一步“加载”过程,要通过一个类的全限定名来获取这个类的二进制字节流。这个过程,是要借助于一股虚拟机外部的工具来进行的,这一工具就是类加载器。每一个类,都有一个针对他的类加载器。两个类是否相同,不但要比较他本身,还要比较他们的类加载器。

类加载器可以分为四类:

启动类加载器:由C++编写,属于虚拟机的一部分,是属于很基础的加载器,作用是。
扩展类加载器:可以由开发者使用
应用类加载器:也叫做系统类加载器,加载用户类路径上自己指定的类,我们平时使用也基本是使用这个。
而具体的加载逻辑,被称为“双亲委派模型”,即首先有一个根部的加载器“启动类加载器”,其下有一个儿子叫“扩展类加载器”,其下是“应用程序类加载器”,最后是“自定义类加载器”。具体流程:

一个类收到了加载的请求,首先会把请求委托给父类加载,每一个加载器都是如此。这样最终会把请求交给根节点的“启动类加载器”。之后如果父加载器可以加载,就会直接加载。否则,会将请求再传下来。

虚拟机优化

Java的编译期,是一个极不确定的过程。因为Java的编译期很多,有前端编译期,有后端编译器,还有静态提前编译器。前端编译期负责将.java转化为简单的.class,后端编译器负责将字节码转换为机器码,如JIT。静态提前编译器会将.java直接翻译为本地机器码,如AOT。因此,编译期并不能很精准的分类,因此只能大概分为“早期”和“晚期”。

早期优化

早期阶段,可以概括的看做前端编译器将.java转化为.class的过程。这一阶段的优化又可以称作编译期优化。

这一阶段其实和其他语言的编译期优化类似,无非就是词法、语法分析,语义分析,然后做一些语言层面的优化。比如,语法糖、注解的处理,还有字符串拼接。Java语法糖不多,但是挺实用的,诸如类型擦除啊,自动拆箱、装箱啊。注解是在编译时进行优化,具体在运行时才会体现出作用。还有一个例子,我们都知道String StringBuilder StringBuffer区别。都说每次用"+"链接两个字符串的时候都会new一个String,这样会很耗内存。其实这个说法并不全对。如果仅仅是一个个拼接,哪怕是换行,编译器如果识别到,都会为我们优化,即将他们作为一个String对象。只有个别情况,比如在循环结构中频繁的链接字符串,才会出现刚才说的那个问题。

运行期优化

运行期优化,比较熟知的比如JIT和AOT。虚拟机之所以这样分开,是为了增加虚拟机扩展性,也就是说普通的前端编译期只接受Java。而后端编译器则可以接受像Groovy等语言。同时JIT和AOT对编译的性能优化很大,因此也就被选作Android中Java虚拟机所使用的编译器了。

先说JIT,他是将字节码转换为了机器码,这是DVM采用的编译器。他的特点可以打个比方,比如让你背一首诗,而且还要当着我的面背出来,还要重复背好几次,那么你肯定需要背好久,才能一次念出来。通过JIT,我可以让你照着书,看一个字背一句。这样背起来就很轻松了。但是JIT也不一定真的就远比普通的解释器执行慢。在JVM中,JIT是针对热点代码的,对于这些代码才会进行JIT编译。因此JIT就编译本身转化过程而言也是比较慢的,快是快在执行上。还是那个例子,如果只让你大概总结一下意思,就背几句诗,那么你翻书还不如直接背的快。而对于热点诗句,你能看一眼念一句,那么这个速度是相当快的。

双亲委派模型

其实只有父类,没有mother,也不是平时说的那种继承关系,只是调用逻辑是这样

什么是双亲委派?

方法区中存储着 类 相关的信息,为索引或者其他必要的操作提供了很多方法,只是这些方法开发人员几乎接触不到罢了(方法区更像是一个提供了管理类等操作的容器)。

Java为什么要设计双亲委派模型?

采用双亲委派模型使得java类随着它的类加载器一起具备了一种带有优先级的层次关系,保证java程序稳定运行
有时不得不破坏:JDBC,因为有不同的数据库厂商,Bootstrap ClassLoader就得委托子类来加载不同厂商提供的具体实现

双亲委派模型过程:

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务则成功返回,只有父加载器无法完成此加载任务时才自己去加载

反射

java反射是指在运行状态中动态获取指定类以及类中的内容(成员),并调用其任一方法的技术,换句话说使用反射任何类对我们来说都是透明的,可获取任何东西,破坏程序安全性?

反射能做什么?
运行时判定任意一个对象所属的类
运行时构造任意一个类的对象
运行时判定一个类具有的成员变量和方法
运行时调用任意一个对象的方法
生成动态代理
java.lang.Class; //类
java.lang.reflect.Constructor;//构造方法
java.lang.reflect.Field; //类的成员变量
java.lang.reflect.Method;//类的方法
java.lang.reflect.Modifier;//访问权限
获取class对象的三种方式:

JVM内存模型

内存模型

注意区分内存模型和内存结构:
JVM中的堆、栈、方法区等是java虚拟机的内存结构
深入理解JVM虚拟机一书上说Java内存模型是JVM的抽象模型主内存,本地内存等
尝试一句话概括java内存模型:保证多线程之间操作共享变量的正确性
什么是内存模型?像cpu中的L1,L2缓存等,这样的结构提高了数据访问性能,但带来了同步的问题(如两个cpu去操作同一个地址),所以在CPU的层面,内存模型定义了一个充分必要条件,保证其它CPU的写入动作对该CPU是可见的,而且该CPU的写入动作对其它CPU也是可见的,那么这种可见性该如何实现?有的提供了强内存模型,有的提供了若内存模型+特殊指令(如内存屏障)
在Java内存模型中,描述了在多线程代码中,哪些行为是正确的、合法的,以及多线程之间如何进行通信,代码中变量的读写行为如何反应到内存、CPU缓存的底层细节。如final,volatile和synchronized等
分哪几块存储区,各个存储区的作用
常常直接接触到的是运行时数据区,可以细分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器。这几个区域中,方法区和堆是所有线程共享的,所有线程都可以访问,而虚拟机栈、本地方法栈、程序计数器是线程隔离的,每个线程有自己独立的区域,线程之间是不共享的。

程序计数器:相当于一个程序执行过程中的行号指示器,类似于操作系统中的ip,指向当前执行的虚拟机字节码地址。如果执行的是Java方法,计数器就记录者正在执行的虚拟机字节码指令的地址。如果是native 方法,计数器为空

虚拟机栈:虚拟机栈就是java方法的内存模型,每一个线程在执行时会有自己的一个虚拟机栈,在运行过程中把所调用方法封装为一个栈帧,然后将栈帧存放在栈里面。栈帧包含了一个方法执行时的相关信息,包括方法用到的局部变量,操作数,动态链接等。
本地方法栈:类似于虚拟机栈,只不过他存放的是Native方法。
堆:堆是相对来说占内存最大的一块,用来存放所有线程创建的类的对象实例。方法调用中如果创建了对象,会把这个对象实例存放在堆,然后将对于这个对象的引用存放在栈中,这样就可以方法对象了。对于内存的回收,也就是对堆内存的回收了。
方法区:存放虚拟机加载的类的信息和一些常量、静态变量等,这些内容一般是不可变的。

常见垃圾回收算法

转载地址:http://ovphn.baihongyu.com/

你可能感兴趣的文章
复盘:一个创业项目的失败之路
查看>>
阿里巴巴宣布加入Linux基金会
查看>>
为什么你应该尝试 “全栈”
查看>>
程序员什么时候该考虑辞职
查看>>
如何写一本书?
查看>>
加班能体现编程的热情吗?
查看>>
Hadley Wickham:一个改变了R的人
查看>>
glibc 指导委员会解散声明
查看>>
Linux创始者托瓦兹谈及IoT --「安全在其次」
查看>>
传感器数据分析(Sensor Data Analytics)是什么?
查看>>
智能硬件开发如何选择低功耗MCU?
查看>>
阿里感悟(十)如何写好简历
查看>>
阿里感悟(十一)如何准备面试
查看>>
软件架构入门
查看>>
80 多个 Linux 系统管理员必备的监控工具
查看>>
OOD的原则
查看>>
Tool to trace local function calls in Linux
查看>>
Linux 下查询 DNS 服务器信息
查看>>
ulimit 里的 file size 的 block 单位是多少?
查看>>
linux下查看端口对应的进程
查看>>