1.jvm家族成员:sun-hotspot,ibm-J9,bea-jrockit
2.jdk8的新增功能:
- 对lambda表达式的支持,让Java语言拥有了流畅的函数式表达能力
- 内置nashorn javascript引擎,增强jvm对javascript的运行能力
- 新的时间、日期API
- 彻底移除永久代(用本地内存的元空间meta-space代替永久代实现方法区)
- Java Mission Control监控工具
3.句柄:用来记载数据地址的变更,标识对象或者项目的标识符由来(一种虚拟地址)
4.hotspot诸多特点:
- 热代码探测:把反复执行的代码编译成机器码,下次执行就可以直接执行,不用出发解释(和执行计数器配合使用)
- 解释器:一种计算机程序,它直接执行编程语言代码,不会将代码预编译为机器码
- 编译器:将高级语言写的程序转变为机器语言
- JIT:即时编译。对于经常使用的字节码,会把该代码的整个方法为单位,一次性将整个方法的字节码编译为本地字节码
- OSR:栈上替换编译。运行时替换正在运行的函数、方法的栈帧技术
二.内存区域及内存溢出异常
1.运行时数据区域
运行时数据区域 | |
方法区(Method area) | 虚拟机栈(vm stack) |
本地方法栈(native stack) | |
堆(heap) | 程序计数器(pcr) |
注:蓝色背景表示线程共享区域
- 虚拟机栈:每个方法的执行,jvm都会创建一个栈帧用于存储局部变量,操作数栈,动态连接,方法出口等信息。
- 堆:Java对象和数组都在堆上分配。垃圾回收器管理的内存区域.通过xms(启动获取内存)和xmx(最大可扩展内存)来设置扩展实现。
- 方法区:存放常量池、静态变量
2.对象的创建:
- 类加载
- 查询对应的类信息
- 分配内存空间
分配方式 | java堆是否规整(果) | gc是否带有空间压缩功能(因) |
指针碰撞 | 是 | 带 |
空闲列表 | 否 | 不带 |
3.cas:比较再交换,乐观锁。区别于Synchronized悲观锁。java中的AutomicXX类底层均是采用cas。
内存值v | 完全相等才会将B赋值给v |
预期值A | |
新值B |
4.分配内存的线程安全问题解决方案:
- cas+失败重试保证原子性
- 使用本地缓冲区(thread local allocation buffer ,tlab):每个线程拥有自己的缓冲区,只有本地缓冲区用完了才会同步锁定。
5.对象的内存布局:
对象在堆内存的存储布局:对象头header,对象体body和对齐填充
位置 | 作用 |
对象头header | mark word:存储自身的运行时数据。如线程id,hashcode,锁状态,gc分代年龄等 |
类型指针:指向它的类型元数据的指针 | |
对象体body | 存放实列数据 |
对齐填充(4k对齐) |
6.内存泄漏(memory leak):GC无法回收对象,对象应当被回收
内存溢出(memory overlow):对象都是有用的
三.垃圾收集器和内存分配策略
1.哪些内存需要回收?
线程独享的区域生命周期和线程一样。方法或者线程结束时区域自动回收。
编译器即可确定内存大小的不用动态回收。Java堆和方法区只有在运行期我们才能知道程序究竟会创建哪些对象。
2 对象是否已死亡?
- 引用计数法:对象互相引用特例
- 可达性分析:
GC ROOTS根对象:通俗讲就是一组存活的元素,比如常量引用对象,类静态属性引用的对象等
3. 引用类型
存在的意义:这个世界并不是非黑即白,同样,Java对象也不是只有‘被引用’和‘未被引用’两种状态。可以是内存足够时存活,内存不足时抛弃。
实现:Java.lang.ref包下。分为强引用(我们平常使用的都是这种),软引用,弱引用,虚引用四种。
- 强引用:只要强引用存在,对象就永远不会被回收
- 软引用:还有用但非必须。利用软引用实现告诉缓存组件 weakhashmap
- 弱引用:只能生存到下一次GC
- 虚引用:不影响对象的生存时间,唯一目的时为了对象被回收时能接收到一个系统信息
4.jvm如何判断一个对象是否死亡?过程如何?
- 可达性分析,不可达的话第一次标记
- 筛选是否有必要执行finalize()【任何一个对象的finalize方法都只会被系统自动调用一次】
- 有必要---》放入 F-Queue队列中-----》jvm线程执行,但不保证结束
- 自救,再次和任一对象建立连接
- 未自救成功,第二次标记-----》等待死亡的降临
5.回收方法区:主要针对常量池和对类型的卸载
6.垃圾收集算法:
- 分代收集理论:
建立基础 弱分代假说:大部分对象都是朝生夕死 强分代假说:熬过越多次GC的对象越难以消亡 具体实现 Java堆划分出不同区域后,每次回收区域不一样,各区域算法也不同 minor gc marjor gc full gc 跨代引用 新生代上建立1个记忆集数据结构。记录老年代中存在跨代引用的部分。minorgc 时将这个内存里的对象加入到GcROOTS中
算法类型 标记-清除算法:标记过程就是判断对象是否死亡的过程 缺点:执行效率不稳定
产生大量的空间碎片
标记-复制算法(半区复制):将空间分成多块,每次GC复制活的对象到另一块,然后清空整个区域。新生代多用这种算法 缺点:空间浪费,要有分配担保 标记-整理算法:标记-》存活对象移到一端-》以边界清理。老年代多使用此算法 新生代回收机制(标记-复制)为何是1个eden,2个survior?
From survior,To survior。两个区域轮流充当空闲区域
2.伪共享:多个线程操作的不同对象处于同一个缓存行cacheline,就会彼此影响导致下性能下降。(原因:MESI缓存一致性和RFO协议)
3.cpu缓存机制:(volatile能够实现可见性的原因就是MESI和cpu缓存机制)
背景 | 为解决cpu处理速度远高于内存读写速度,新增cpu高速缓存 | |
级别 | L1:离得距离最近,速度最快,空间最小 | 只能别单个cpu单独使用 |
L2 | ||
L3:离的最远,速度最慢,空间最大 | 一个卡槽上的所有cpu共同使用 |
四.虚拟机加载机制
1.类的生命周期:
加载 | 通过全限定名获取二进制字节流 | |
将静态存储结构转化为方法区的运行时数据结构 | ||
在内存中生成java.lang.class对象 | ||
连接 | 验证 | 保证class文件符合规范,不会危害虚拟机的安全 |
准备 | 为静态变量分配内存并设置默认初始值(被final修饰的静态变量特例,会在准备阶段就直接赋值指定的值) | |
解析 | 将常量池内的符号引用(用一组符号字面量表示引用的对象)替换为直接引用(句柄、指针) | |
初始化 | ||
使用 | ||
卸载 |
2.常量池优化机制:
- 如果池中有像创建的对象,则直接将指针赋值给该变量
- 如果是几个字符组成的字符串,变成字符串常量时也要遵循上一条规则
- 常量优化只针对常量,有变量的不可以
3.双亲委派类加载结构:通过组合模式复用父类加载器的功能
启动类加载器bootstrap class loader | rt.jar/tool.jar |
扩展类加载器extension class loader | jre/lib/ext |
应用程序类加载器 appplication class loader | classpath |
五.程序编译与代码优化
1.语法糖:对语言的编译和功能没有影响的一种语法,例如Java中的泛型
2.拆装箱陷阱:Java中的包装类型底层使用了享元模式,预先准备了一定范围的数值。例如Integer的范围在[-128,127]不用新建,超出范围需要新建对象。
此外,.equals()不处理数据转型的关系
六.Java内存模型与线程
1.TPS:每秒事务处理数,代表1秒内服务端能响应的请求总数
2.内存模型:
主要目的:定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到内存和从内存中取出变量值。
所有的变量都存储在主内存,每个线程有自己的工作内存,里面保存了该线程使用的变量主内存副本,线程对变量的所有操作都必须在工作内存里进行,不能直接读写主内存数据。
3.volatile :
语义 | 保证多线程情况下变量值的可见性 | 机器码lock会将本处理器的缓存写入内存,引起其他处理器的缓存无效化,重新读取内存数据 |
禁止指令重排序 | lock禁止将其后面的指令重排序到其前面去,相当于内存屏障的作用 | |
例子 | dcl(double check lock)懒汉式单例模式 |
4.java内存模型概述:
线程安全的实质 | 如何保证 |
原子性 | 默认基本数据类型的访问,读写都具有原子性 |
synchronized | |
可见性 | synchronized |
volatile | |
final | |
有序性 | volatile |
synchronized |
5.Java先行发生原则:
程序次序规则 | 单个线程内表现为串行 |
管程锁定规则 | 同一个锁必定是先释放才能被获取 |
volatile变量规则 | |
线程启动规则 | |
线程终止规则 | |
线程中断规则 | |
对象终结规则 | |
传递性 |
6.Java与线程(cpu资源调度的最基本单位):
线程的实现 | 内核线程(1:1) | 内核是操作系统的最基本部分。为众多的应用提供对计算机硬件的安全访问的一部分软件 | |
用户线程(1:n) | |||
用户线程+轻量级进程(n:m) | |||
Java线程调度 | 协同式 | 线程自己决定执行时间 | |
抢占式(java) | 系统控制(java添加了10各级别的线程优先级) | ||
Java线程状态 | new | ||
running | start() | 可能正在运行,也可能在等待分配时间片 | |
waiting | sleep():不释放锁,waiting():释放锁,notify() | 无限期等待,需要其他线程的显式唤醒 | |
time_waiting | 一定时间后系统自动唤醒 | ||
blocked | synchronized | 等待获取一个排他锁 | |
terminate | run()结束 |
7.线程安全
按照由强到弱,java中各种操作共享数据可分为 | 不可变 | final+基本数据类型 |
非基本数据类型的对象状态不能被改变。例如String的任何方法都不会改变它原来的值 | ||
绝对安全 | 基本不存在,代价很大 | |
相对安全(我们平常说的线程安全) | 单次的操作是安全的,多线程时可能需要在调用端使用同步手段保证安全,例如vector | |
线程兼容 | 对象本身不是线程安全的,需要在调用端使用同步手段保证安全,例如arraylist | |
线程对立 | 不管采用什么措施,都不能保证并发安全,应尽量避免。例如suspend()和resume() |
8.如何保证线程安全
互斥--》同步 | 悲观锁 | synchronized | 可重入锁 | |
reentrantlock(lock必须在finally块中释放,否则出现异常可能导致永远不会释放锁) | 等待可中断 | |||
公平锁(性能较差) | ||||
所绑定多个条件(condition) | ||||
非阻塞同步 | 乐观锁 | cas无锁编程 | 存在ABA漏洞 | |
无同步方案 | 可重入代码 |
4.锁优化
偏向锁 | 无竞争,只有一个线程,不需要同步。如果程序中大部分锁都存在竞争,则应该关闭偏向锁。(偏向锁的threadid占用了原本hash码的空间,hash码不应该变化,所以计算过hash的对象无法再进入偏向锁状态) |
轻量级锁 | 无竞争,cpu自旋获取锁,不阻塞 |
重量级锁 | 阻塞 |