jvm高级特性整理

来源:互联网 发布:网络寻凶第一季百度云 编辑:程序博客网 时间:2024/05/16 06:53
1.1991年开始启动,java的前身oak,james gosling博士;1995年改名java;1996年jdk1.0发布;2002年jdk1.4发布,走向成熟;2004年jdk1.5发布,加入自动装箱、泛型、动态注解、枚举、可变长参数、foreach等,还有concurrent并发包;2006年jdk1.6发布改名java se6 java ee6等,不再叫j2se j2ee;2009年jdk1.7,被oracle收购;2014年jdk8


2.运行时数据区域:程序计数器、java虚拟机栈、本地方法栈、java堆、方法区
程序计数器:当前线程所执行的字节码的行号指示器,改变这个计数器的值来选取下一条需要执行的字节码指令;java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现,线程切换后能恢复到正确的执行位置,所以线程私有;native方法的计数器为空

java虚拟机栈:线程私有,生命周期与线程相同,每个方法被执行的时候会创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等,一个方法调用完成的过程就是从入栈到出栈的过程
一般说的栈就是虚拟机栈中的局部变量表,存放编译期可知的各种基本数据(boolean、byte、char、short、int、float、long、double)、对象引用和returnAdress类型
局部变量表所需的内存空间在编译期间完成分配(完全确定分配多大内存),在运行期间不会改变局部变量表的大小,最小单位Slot,每个slot可重用,虚拟机通过索引定位的方式使用局部变量表

本地方法栈:跟虚拟机栈作用相似,虚拟机栈为虚拟机执行java方法服务,而本地方法栈则为虚拟机使用的native方法服务,sun hotspot虚拟机直接就把两者合二为一

java堆:被所有线程共享的一块内存区域,在虚拟机启动的时候创建,唯一目的就是存放对象实例;主流虚拟机都是按照可扩展来实现(通过-Xmx和-Xms控制),如果堆中没有内存完成实例分配,并且堆也无法再扩展时将会抛出OutOfMemoryError异常

方法区:线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;有时称为永久代,这个区域的内存回收目标主要针对常量池的回收和对类型的卸载
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,将在类加载后存放到方法区的运行时常量池中;具备动态性,运行期间也可能将新的常量放入池中(如string类的intren()方法)

3.主流对象访问方式:使用句柄和直接指针;使用句柄,java堆中会将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例数据(java堆)和类型数据(方法区)各自的具体信息地址;直接指针,reference中存储的就是对象地址,节省了一次指针定位时间的开销,速度更快


4.垃圾收集:主要指java堆跟方法区,前面三个区域都随线程而生而灭,内存在编译期具备确定性,方法或者线程结束就回收了。
堆一般回收一次70-95%,方法区(永久代)主要回收废弃常量和无用的类
判断对象是否存活:
-引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用时加1,当引用失效时减1,任何时刻计数器都为0的对象就是不可能再被使用(java没有采用这种方法,因为很难解决对象之间的相互循环引用问题)
-根搜索算法:通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象的GC Roots没有任何引用链相连时,证明此对象是不可用的;java中可作为GC Roots的对象有:虚拟机栈中的引用对象,方法区中类静态属性引用的对象,方法区中的常量引用的对象,本地方法栈中JNI(Native方法)的引用的对象

5.jdk1.2后分为强引用、软引用、弱引用、虚引用;只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象;软引用描述一些有用但非必需的对象,其关联的对象在系统将要发生内存溢出异常之前列进回收范围之中并进行第二次回收;弱引用也是描述非必需对象,只能生存到下一次垃圾收集发生之前;虚引用对其生存时间不构成影响,也无法通过虚引用取得一个对象实例,为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知

6.判断一个无用的类:该类所有的实例都已经被回收,java堆中不存在该类的任何引用;加载该类的ClassLoader已经被回收;该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射该类的方法

7.垃圾收集算法
标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象;缺点:效率低,产生大量不连续内存碎片占有内存
复制算法:将内存按容量划分大小相等两块,每次只使用其中一块,一块用完后就将还存活的对象复制到另一块,然后再把已使用的内存空间一次清理掉,缺点:需要更大内存;商业虚拟机采用此方法回收新生代:分为一块较大的Eden空间和两块小的Survivor空间,每次使用Eden和其中的一块Survivor,回收时将这两块还存活的对象一次性拷贝到另一块Survivor空间,最后清理掉Eden和刚才用过的Survivor空间;Eden:Survivor大小默认8:1,如果另一块Survivor没有足够的空间就直接通过分配机制进入年老代
标记-整理:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存;回收年老代
分代收集算法:根据对象存活周期的不同将内存划分为几块

8.对象优先在Eden分配,当没有足够空间的时候虚拟机发起一次minor gc
  大对象直接进入老年代,大对象是指需要大量连续内存空间的java对象
  长期存活的对象将进入老年代


9.高性能硬件上部署程序,主要有两种方式
  )通过64位jdk来使用大内存
  )使用若干个32位虚拟机建立逻辑集群来利用硬件资源

10.集群间同步导致的内存溢出
   堆外内存导致的溢出错误
   外部命令导致系统缓慢
   服务器jam进程崩溃

11.jps:虚拟机进程状况工具,可以列出正在运行的虚拟机进程,并显示虚拟机执行主类的名称
   jstat:虚拟机统计信息监视工具,显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、jit编译等运行数据
   jinfo:java配置信息工具,实时查看和调整虚拟机各项参数
   jmap:java内存映像工具,用于生成堆转储快照(dump文件),还可以查询finalize执行队列,java堆和永久代的信息
   jhat:虚拟机堆转储快照分析工具
   jstack:java堆栈跟踪工具,生成虚拟机当前时刻的线程快照,用于定位线程出现长时间停顿的原因

12.java中类型的加载和连接过程都是在程序运行期间完成的
类的整个生命周期:加载、验证、准备、解析、初始化、使用、卸载
加载:1)通过一个类的全限定名来获取定义此类的二进制字节流
     2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
     3)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
验证:连接阶段的第一步,为了确保Class文件中的字节流中包含的信息符合当前虚拟机的要求,且不会危害虚拟机自身安全
     文件格式验证、元数据验证、字节码验证和符号引用验证四个阶段
准备:正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配(不包括实例变量的初始化,它将在对象实例化的时候分配在java堆中)
解析:虚拟机将常量池内的符号引用替换为直接引用的过程
初始化:执行类构造器<clinit>()方法的过程

13.类加载器:启动类加载器、扩展类加载器、应用程序类加载器

14.虚拟机字节码执行引擎(解释执行、编译执行)

15.javac编译过程:解析与填充符号表过程;插入式注解处理器的注解处理过程;分析与字节码生成过程
解析与填充符号表:1)词法语法分析:词法分析是将源代码的字符流转变为标记(Token)集合,语法分析是根据Token序列来构造抽象语法树的过程;2)填充符号表:符号表由一组符号地址和符号信息构成的表格,可用于语义检查,地址分配的依据
注解处理器:在处理注解期间对语法树进行修改,编译器将回到解析及填充符号表的过程重新处理
语义分析与字节码生成:语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,分为标注检查和数据及控制流分析两个步骤;通常使用语法糖能够增加程序的可读性,减少代码出错的机会;字节码生成是java编译过程最后一个阶段,把语法树、符号表等转化成字节码写到磁盘中

16.解释器和编译器
当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行;当程序运行后,随着时间推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率

hotspot虚拟机内置量两个即时编译器client compiler和server compiler,采用分层编译的策略

“热点代码”会被即时编译器编译:被多次调用的方法;被多次执行的循环体。热点探测:基于采样;基于计数器(方法调用计数器,回边计数器)


17.即时编译器编译优化技术
1)方法内联:除去方法调用成本,为其他优化建立良好基础
2)进行冗余访问消除
3)复写传播
4)无用代码消除
5)公共子表达式消除
6)数组边界检查消除
7)逃逸分析


18.java内存模型:屏蔽各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果
    主内存与工作内存:每条线程的工作内存保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行


19.内存间交互操作:lock(锁定)、unlock(解锁)、read(读取)、load(载入)、use(使用)、assign(赋值)、store(存储)、write(写入)

20.volatile:一个变量定义成volatile之后,具备两种特性,
第一是保证此变量对所有线程的可见性;但并发下并一定是安全的,因为java里面的运算操作并非原子操作,由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中,仍需要通过加锁(使用synchronized或java.util.concurrent中的原子类)来保证原子性
1)运算结果并不依赖变量的当前值,或者确保只有单一的线程修改变量值
2)变量不需要与其他状态变量共同参与不变约束
第二个特性是禁止指令重排序优化
volatile变量的读操作的性能消耗与普通变量几乎没啥差别,但是写操作可能会慢一些,因为需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行,在volatile与锁中选择的唯一判断依据仅仅是volatile的语义能否满足使用场景的需求


21.允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现选择可以不保证64位数据类型的load、store、read、write这四个操作的原子性,这就是所谓的long或double的非原子性协定,目前商用虚拟机几乎把64位数据的读写操作作为原子操作来对待


22.java内存模型围绕在并发过程中如何处理原子性、可见性和有序性三个特性来建立的
可见性:(volatile、synchronized和final修饰)
有序性:线程内表现为串行的语义,观察另一个线程则是无序的



23.线程的实现主要有三种方式:使用内核线程实现,使用用户线程实现,使用用户线程加轻量级进程混合实现


24.一条java线程就映射到一条轻量级进程之中,线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种:协同式线程调度和抢占式线程调度,java使用抢占式,java线程是被映射到操作系统的原生线程上来实现的,线程调度最终还是由操作系统说了算
java进程5个状态,任一时刻有且只有一种状态:新建、运行、无限期等待、限期等待、阻塞、结束
java中各种操作共享的数据分为五类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立
线程安全的实现方法:互斥同步(悲观)、非阻塞同步(乐观)、无同步方案


25.锁优化:
1)自旋锁与自适应自旋
2)锁消除
3)锁粗化
4)轻量级锁
5)偏向锁

 
1 0