虚拟机
http://www.bubuko.com/infodetail-2242063.html
1996 jdk1.0 (classic vm 虚拟机)
1997 jdk1.1
1998 jdk1.2 (javaee,javase区分)
2000 jdk1.3 (hotspot虚拟机发布)
2002 jdk1.4 (解密类库,正则表达式)
2004 jdk1.5 (泛型,注解,装箱,枚举)
2004 jdk1.6 (jdbc4.0,脚本语言支持)
2011 jdk1.7 (新的垃圾回收器G1)
2014 jdk1.8 (Lambda表达式)
2016 jdk1.9 (模块化)
oracle 两个虚拟机 JRockit和HotSpot。jdk8要进行整合成一个。
只要符合jvm语言规范的标准,什么语言都可以在jvm中运行。
jvm保存数据使用的是2进制,2进制是8位,第一位是符合位,1代表负数。
jvm为什么要使用补码? (正负数的补码相同)
正数的原码,反码,补码均相同。
负数的反码在原码的基础上,最高位(符号位)不变,其他位0变1,1变0,补码再在反码的基础上加1
0
正数:0000 0000
负数:1000 0000
反码:1111 1111
补码:0000 0000
-6+5 的运算?
-6的原码:1000 0110
-6的反码:1111 1001 (+1获得补码)
-6的补码:1111 1010
5的原码:0000 0101
5的补码:0000 0101
1111 1010
- 0000 0101
= 1111 1111 (符号位参与运算)
-1
= 1111 1110(反码)
= 1000 0001 (原码)
jvm内存空间
堆 :1.对象实例 2.线程共享 3.GC主要工作区 4.数组 5.堆的划分
方法区 :
虚拟机栈 :1.线程私有 2.栈是由栈帧组成,每个帧是一个方法(静态和非静态)。3.局部变量
Native栈 :
计数器 :
对象的栈上分配:1)小对象 2)直接分配在栈上,可以自动回收,减轻gc压力
可见性:一个线程修改了变量,其他线程可以立刻知道
保证可见性的方法:
1)volatile :
2)synchronized
3)final : 被定义成常量的变量
线程的分类
1)守护线程:jvm自己使用的线程,例如:gc线程
2)普通线程:jvm中只要还存在普通线程,jvm就不会停止。
jvm加载并解析一个类后,将类信息保存到方法区,创建的对象保存对堆中。(方法区和堆是内存共享的)。
当一个线程被创建时,会被分配一个程序计数器(pc寄存器)和 虚拟机栈。虚拟机栈保存了该线程调用方法状态,
一个方法一个栈帧,里面保存了方法的局部变量,方法参数,返回值。当方法执行时,该帧进入栈,当执行完成,
帧出栈。计数器保存该线程执行的行号。
堆:
当创建一个类的实例或者数组时,都在堆中为新对象分配内存。jvm中只要一个堆,所有的线程都共享他。
jvm的生命周期:
执行java程序,程序开始执行jvm开始运行,当程序结束时它就停止。
同一台机子上运行三个程序,就会运行三个jvm。java的线程分两种,
守护线程和普通线程。只有当jvm中没有普通线程执行时,jvm就会停止。
守护线程是jvm自己使用的线程,比如GC线程。
1个字节 占8位
int 4个字节 32位
15.为什么要使用16进制?
人写的是10进制,转换为2进制的话,是32位太长了。
使用16进制就短了很多
16.class文件结构
魔数(4字节)+ class文件版本号(4个字节)+常量池 + 访问标志
- 索引(知道父类是谁)+ 字段表集合(全局和类变量)+ 方法表集合
- 属性表集合
常量池 = 字面量(字符串和常量) + 符号引用(类信息+方法描述)
17.操作数栈和寄存器的架构区别?
18.类加载时期:
加载 验证 准备 解析 初始化 使用 卸载
19.类进行初始化的时机?
主动引用(会进行初始化)
1)使用new关键字实例化对象
2)读取一个类的静态字段
3)调用一个类的静态方法
4)使用反射调用类
5)子类初始化时,发现父类未初始化,父类进行初始化
6)包含main()方法的主类
被动引用(不会进行初始化)
1)通过子类引用父类的静态字段,不会导致子类初始化
2)通过数组定义来引用类,不会触发此类的初始化 SupperClass[] sc = new SupperClass[10];
3) 使用(public static final String XXX)修饰的常量,不会初始化该类。
20.类的加载的过程,jvm需要做的事?
1)通过类全名获取该类的2进制流
2)将字节流转换为方法区可识别的数据结构
3)在堆中创建该对象,作为方法区这个类的访问入口。
21.类的验证过程?
主要目的就保证class文件符合jvm规范标准
1)文件格式验证(魔数,主次版本号,常量池)
2)元数据验证
- 该类是否有父类
- 父类是否继承了不允许继承的类
- 该类不是抽象类,父类是抽象类,是否都实现了
- 父类和子类之间的方法命名是否有冲突
3)字节码验证(保证程序语言 语义合法符合逻辑)
4)符号引用验证 - 类全名是否能找到该类
- 通过字段描述是否找到方法和字段
- 引用类的访问性能否被当前类访问
22.类的准备过程?
为类变量(static修饰的)分配内存并设初始值(数据类型的初始值0)。
这个阶段内存分配仅包括类变量,不包括实例变量,实例变量将会在对象
实例化时,随着对象一起分配到java堆中。
23.类的初始化?
开始执行类中程序代码
24.类加载器
- 启动类加载器:将类库加载到虚拟机中
- 扩展类加载器:负责加载第三方类库
- 应用程序类加载器:
25.栈帧结构:
- 栈帧是用于支持虚拟机进行方法调用和执行的数据结构,其中保存了
局部变量表,操作数栈,动态链接,方法返回地址。 - 局部变量表:方法参数+局部变量
- 操作数栈:方法执行时,各种字节码指令会出入
- 动态链接:链接运行时常量池引用(具体也没明白)
- 方法返回地址:
-
通过javap指令来获取类的字节码
-
可以眼看反编译class文件到java文件
-
mat来检测内存溢出,lint 检测代码规范
-
内存溢出:
- 堆溢出 (原因:对象太多 解决办法:增大堆空间)
- 永久区溢出 (原因:类的数量太多 解决办法:增大perm区;允许class回收)
- jvm栈溢出 (原因:创建线程的需要分配的栈空间 > 操作系统分配的空间
解决办法:减少堆空间;减少线程栈的大小) - 直接内存溢出 (原因:操作系统分配空间 < 堆空间+线程栈空间+直接内存
解决办法:触发GC)
-
内存管理,内存优化,oom
-
图片缓存的两种方式:软引用,Lru算法
-
Lint进行代码检测 mat进行内存溢出检测
-
内存优化
- 数据结构优化
- 频繁使用数据添加使用StringBuilder
- ArrayMap,SparseArray 代替HashMap
- 内存抖动(突然申请大量内存,然后又取消)
- 对象复用
- 复用系统自带资源
- ListView 的 ConvertView复用
- 避免在onDraw方法中执行对象创建
- 避免内存泄漏
- 内存泄漏导致可用Heap越来越少,频繁触发GC
- 尤其Activity的泄漏(内部类引起)
- 使用Application context而不是Activtiy的
- oom问题优化
- 临时Bitmap对象的及时回收
- 加载Bitmap:缩放比例,解码格式,局部加载
- 软弱引用的使用