class文件
可以被JVM
识别,加载并执行的文件格式,记录了一个类文件的全部信息
。html
生成一个class文件,能够经过两种方式:java
- 经过IDE自动帮咱们build
- 手动经过javac去生成class文件
class文件格式详解:android
- 一种8位字节的二进制流文件
- 各个数据顺序紧密的排序,无间隙
- 每一个类或接口都单独占据一个class文件
javap -verbose TestClass
查看字节码内容程序员
class文件弊端:shell
- 内存占用大,不适合移动端
- 堆栈的加载模式,加载速度慢
- 文件IO操做多,类查找慢
dex文件
可以被DVM
识别,加载并执行的文件格式,记录整个工程
中全部类文件的信息。安全
如何生成一个dex文件:数据结构
- 经过IDE自动帮咱们build生成
- 手动经过dx命名去生成dex文件
如何运行dex文件:jvm
//javac -target 1.6 -source 1.6 Hello.java //dx --dex --output Hell.dex Hello.class //adb push Hello.dex /storage/emulated/0 //adb shell进入手机控制台 //dalvikvm -cp /sdcard/Hello.dex Hello
dex文件格式详解
- 一种8位字节的二进制流文件
- 各个流按顺序紧密的排列
- 整个应用中全部Java源文件都放在一个dex中
dex和class文件的区别
- dvm执行的是.dex格式文件 jvm执行的是.class文件 android程序编译完以后生产.class文件,而后,dex工具会把.class文件处理成.dex文件,而后把资源文件和.dex文件等打包成.apk文件。apk就是android package的意思。 jvm执行的是.class文件。
- .class文件存在不少的冗余信息,dex工具会去除冗余信息,并把全部的.class文件整合到.dex文件中。减小了I/O操做,提升了类的查找速度
- dvm是基于寄存器的虚拟机 而jvm执行是基于虚拟栈的虚拟机。寄存器存取速度比栈快的多,dvm能够根据硬件实现最大的优化,比较适合移动设备。
JVM内存结构
四大模块须要掌握的: 1.内存空间划分 2.内存管理 3.gc 4.classloader
内存空间划分
程序计数器(Program Counter Register)
一块较小的内存空间,能够看做当前线程所执行的字节码的行号指示器。若是线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是Native方法,这个计数器值则为空。
Java虚拟机栈(Java Virtual Machine Stacks)
与程序计数器同样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每一个方法在执行的同时都会建立一个栈帧用于存储局部变量表、操做数栈、动态连接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈(Native Method Stack)
本地方法栈与虚拟机栈所发挥的做用是很是类似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
Java堆(Java Heap)
对大多数应用来讲,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被全部线程共享的一块内存区域,在虚拟机启动时建立。此内存区域的惟一目的就是存放对象实例,几乎全部的对象实例都在这里分配内存。
GC要回收的部分
方法区(Method Area)
与Java堆同样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是JVM规范中定义的一个概念,具体放在哪里,不一样的实现能够放在不一样的地方。
运行时常量(Runtime Constant Pool)
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后进入方法区的运行时常量池中存放。
内存管理
何时触发垃圾回收?
- java虚拟机没法再为新的对象分配内存空间了
- 手动调用System.gc()方法(强烈不推荐)
手动调用,加大虚拟机压力
低优先级的GC线程,被运行时就会执行GC
怎么判断对象是否已经“死去”?
常见的断定方法有两种:引用计数法和可达性分析算法,HotSpot中采用的是可达性分析算法。
引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任什么时候刻计数器为0的对象就是不可能再被使用的。
客观地说,引用计数算法的实现简单,断定效率也很高,在大部分状况下它都是一个不错的算法,可是主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的缘由是它很难解决对象之间相互循环引用的问题。
可达性分析算法
这个算法的基本思路就是经过一系列的称为“GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来讲,就是从GC Roots到这个对象不可达)时,则证实此对象是不可用的。以下图所示,对象object 五、object 六、object 7虽然互相有关联,可是它们到GC Roots是不可达的,因此它们将会被断定为是可回收的对象。
引用记数算法 jdk1.2以前使用
可达性算法 jdk1.2以后使用
垃圾收集有哪些算法,各自的特色?
标记 - 清除算法
首先标记出全部须要回收的对象,在标记完成后统一回收全部被标记的对象。它的主要不足有两个:一个是效率问题,标记和清除两个过程的效率都不高;另外一个是空间问题,标记清除以后会产生大量不连续的内存碎片,空间碎片太多可能会致使之后在程序运行过程当中须要分配较大对象时,没法找到足够的连续内存而不得不提早触发另外一次垃圾收集动做。
复制算法
为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,而后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂状况,只要移动堆顶指针,按顺序分配内存便可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免过高了一点。
标记 - 整理算法
复制收集算法在对象存活率较高时就要进行较多的复制操做,效率将会变低。更关键的是,若是不想浪费50%的空间,就须要有额外的空间进行分配担保,以应对被使用的内存中全部对象都100%存活的极端状况,因此在老年代通常不能直接选用这种算法。
根据老年代的特色,有人提出了另一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法同样,但后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清理掉端边界之外的内存。
更多:
Dalvik和JVM不一样
- 执行的文件不一样,一个是class,一个是dex
- 类加载的系统与JVM区别较大
- 能够同时存在多个DVM
- Dalvik是基于寄存器,而JVM是基于栈的
Dalvik和ART的区别
- DVM使用JIT来将字节码转换成机器码,效率低
- ART采用了AOT预编译技术,执行速度更快
- ART会占用更多的应用安装时间和存储空间
AOT安装时候就会将字节码转化为机器码,不须要每次运行时候转化,执行速度更快
可是所须要空间更大
四大引用
强引用
,软引用
,弱引用
,虚引用
,最经常使用的就是强引用
和弱引用
**强引用:**在程序代码之中广泛存在的,相似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
**弱引用:**用来描述一些还有用但并不是必需的对象,使用SoftReference类来实现软引用,在系统将要发生内存溢出异常以前,将会把这些对象列进回收范围之中进行第二次回收。
**弱引用:**用来描述非必需对象的,使用WeakReference类来实现弱引用,被弱引用关联的对象只能生存到下一次垃圾收集发生以前。
**虚引用:**是最弱的一种引用关系,使用PhantomReference类来实现虚引用,一个对象是否有虚引用的存在,彻底不会对其生存时间构成影响,也没法经过虚引用来取得一个对象实例。为一个对象设置虚引用关联的惟一目的就是能在这个对象被收集器回收时收到一个系统通知。
java代码编译过程
class字节码加载过程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中验证、准备、解析3个部分统称为链接。
加载:
“类加载”过程的一个阶段,在加载阶段,虚拟机须要完成如下3件事情:
- 经过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个表明这个类的java.lang.Class对象,做为方法区这个类的各类数据的访问入口。
验证:
链接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。从总体上看,验证阶段大体上会完成下面4个阶段的检验动做:文件格式验证、元数据验证、字节码验证、符号引用验证。
准备:
该阶段是正式为类变量(static修饰的变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这里所说的初始值“一般状况”下是数据类型的零值,下表列出了Java中全部基本数据类型的零值。
解析:
该阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化:
初始化阶段是执行类构造器<clinit>()
方法的过程。<clinit>()
方法是由编译器自动收集类中的全部类变量(static
修饰的变量)的赋值动做和静态语句块(static{}
块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。若是该类存在父类,则虚拟机会保证在执行子类的<clinit>()
方法前,父类的<clinit>()
方法已经执行完毕。所以在虚拟机中第一个被执行<clinit>()
方法的类确定是java.lang.Object
。
类加载器
java经常使用类加载器
从Java虚拟机的角度来说,只存在两种不一样的类加载器:一种是启动类加载器
(Bootstrap ClassLoader
),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是全部其余的类加载器
,这些类加载器都由Java语言实现,独立于虚拟机外部,而且全都继承自抽象类java.lang.ClassLoader
。
从Java开发人员的角度来看,绝大部分Java程序都会使用到如下3种系统提供的类加载器。
启动类加载器(Bootstrap ClassLoader):
这个类加载器负责将存放在<JAVA_HOME>\lib
目录中的,或者被-Xbootclasspath参数所指定的路径中的,而且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即便放在lib目录中也不会被加载)类库加载到虚拟机内存中。
扩展类加载器(Extension ClassLoader):
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext
目录中的,或者被java.ext.dirs
系统变量所指定的路径中的全部类库,开发者能够直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader):
这个类加载器由sun.misc.Launcher$AppClassLoader
实现。因为这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者能够直接使用这个类加载器,若是应用程序中没有自定义过本身的类加载器,通常状况下这个就是程序中默认的类加载器。
通常都是由这3种类加载器相关配合进行加载,若是有必要,还能够加入本身定义的类加载器.
双亲委派模型
若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,所以全部的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈本身没法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试本身去加载。
双亲委派模型好处
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一块儿具有了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,不管哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,所以Object类在程序的各类类加载器环境中都是同一个类。相反,若是没有使用双亲委派模型,由各个类加载器自行去加载的话,若是用户本身编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不一样的Object类,Java 类型体系中最基础的行为也就没法保证,应用程序也将会变得一片混乱。
Android中的ClassLoader
BootClassLoader加载Framework class文件
PathClassLoader加载已经安装到系统APK的class文件
DexClassLoader加载指定目录中的class字节码文件
BaseDexClassLoader前面两个的父类
//获取应用中须要加载的classLoader ClassLoader classLoader = getClassLoader(); if(classLoader != null){ Log.e("TAG", "classLoader:" + classLoader.toString()); while (classLoader.getParent() != null){ classLoader = classLoader.getParent(); Log.e("TAG", "classLoader:" + classLoader.toString()); } }
参考
4.Java中new一个对象是一个怎样的过程?JVM中发生了什么?