1.内存管理
1.运行时的内存区域
1.线程私有:虚拟机栈,本地方法栈,程序计数器
2.线程共享:堆,方法区
2.各个内存区域可能抛出的异常
栈
1.单线程时,栈的深度太大,会发生StackOverFlowRrror,比如无穷的递归调用。
2.多线程时,如果不停的创建线程,会出现OutOfMemoryError,因为出去方法区和堆之外,剩下的栈总空间是有限的,不停的创建线程则会不停的申请栈空间,最终会导致内存溢出。
堆
当不停的new对象时,会导致OutOfMemoeryRrror。
方法区
运行时产生大量的类,去填满方法区,比如用CGLib去无穷生成类。
直接内存
使用Unsafe分配本机内存时,可能会导致OutOfMemoeryError
3.各个内存区域容量设置的参数
-Xss2M:设置栈的容量为2M
-Xms10M:设置堆的初始容量为10M
-Xmx10M:设置对的最大容量为10M
-XX:PermSize=10M:设置方法区的初始容量为10M
-XX:MaxPermSize=10M:设置方法区的最大容量为10M
-XX:MaxDirect MemorySize:设置直接内存的最大容量为10M
4.对象的创建
如何在堆中分配内存
根据内存是否规整,即GC回收器是否带有压缩整理功能,分为指针碰撞和空闲列表两种。
如何处理内存分配冲突
1.CAS+失败重试
2.TLAB,即本地线程分配缓冲对象在内存中的布局
1.对象头(哈希吗,GC分代年龄,锁状态标志等)
2.实例数据
3.对象填充如何访问对象
1.通过句柄(栈上的指针执行句柄,句柄中分别有指向对象的指针,和指向类信息的指针)
2.直接指针(栈上的指针直接指向堆中的对象,对象中头部有一个类型指针,指向类型信息。)
5.对象存活判断(如何判断一个对象占用的内存是否该回收)
引用计数法
该方法容易出现循环引用的问题,jvm并没有引用
可达性分析法
判断是否能够从GCRoots中找到一条到达该对象的路径。
GCRoots包括:栈中变量引用的对象,方法区中静态属性(static)引用的对象,方法区中常量(final)引用的对象
6.垃圾回收
引用的种类
1、强引用:通常new出来的对象的引用都是强引用。
2、软引用(SoftReference):如果某次回收完之后,还是可能发生内存溢出,>则进行第二次回收,在第二次回收时会回收软引用,若这次回收后仍是内存不>够,这时候才发生内存溢出。
3、弱引用(WeakReference):其指向的对象只能生存到下一次垃圾收集之>前,无论当前内存是否足够,都会回收弱引用指向的对象。
4、虚引用(PhantomReference):设置虚引用的目的可能是为了,在该对象>被回收前能够得到一个系统通知。finalize方法
方法区的回收
1.废弃的常量:当前系统中没有一个对象引用此常量。
2.无用的类:堆中不存在该类的任何实例,该类的类加载器已经被回收,该类的Class对象没有在任何位置被引用垃圾收集算法
1.JVM整体上是采用“分代收集算法”。
根据对象的存活周期的不同将Java堆分为新生代和老年代。
新生代又分为Eden区,Survivor From区,Survivor To区,其大小比例默认为8:1:1。
Java的方法区被定义为永久代
2.对于新生代,一般采用“复制算法”。因为新生代的存活时间相对较短,复制的时候不会复制太多对象,所以整体效率不至于太低。
当从Eden和Survivor From区向Survivor To区复制时,若Suvivor To区的空间不够,则需要依赖老年代进行“分配担保”。
3.对于老年代,一般采用“标记-清除”(容易产生内存碎片)或“标记-整理”算法。因为老年代的对象存活率较高,若仍是采用复制操作,则需要复制的对象太多,效率会很低。
4.Stop The World
在判断对象是否应该被回收时,是通过GCRoots来判断的。
当枚举GCRoots时,不可以出现在分析过程中,对象的引用关系还在不断发生变化,所以这时候必须停顿所有线程,这种现象称为“Stop The World”。
5.安全点和安全区域
安全区域是指:在这段代码片段内,引用关系不会发生变化。
6.内存分配和回收策略
对象优先在Eden区分配,若内存不够则进行一次Minor GC,将Eden区的活跃对象复制到Survivor To区。
若Survivor To区大小足够,则将其中的存活对象的GC年龄加1,并判断是否应该晋升到老年代区。
若Survivor To区大小不够,则进行分配担保,将对象复制到老年代。
若此时老年代内存大小不够,则进行一次Full GC。垃圾收集器
Serial:新生代收集器,单线程收集器,采用复制算法。
ParNew:新生代收集器,多线程收集器,采用复制算法。
Parallel Scavenge:新生代收集器,多线程收集器,侧重于提高程序运行的吞吐量。
Serial Old:老年代收集器,单线程收集器,采用标记-整理算法。
Parallel Old:老年代收集器,多线程收集器,采用标记-整理算法。
由于多线程的老年代收集器可以充分利用服务器多CPU的处理能力,所以常用Parallel Scavenge/Parallel Old组合,亦提高了吞吐量。
CMS(Concurrent Mark Sweep)
老年代收集器
初始标记–>并发标记–>重新标记–>并发清除
G1收集器
初始标记–>并发标记–>最终标记–>筛选回收
2.执行子系统
(一)类文件结构
(二)类加载机制
(三)字节码执行引擎
3.代码优化
4.并发
1.jvm内存模型
1、JVM内存模型主要是定义程序中变量的访问规则,即在虚拟机中将变量存储到内存,和从内存中取出变量这样的细节。这些变量指的是实例字段、静态字段等,不包括局部变量(因为局部变量是线程私有的,不被共享,不存在竞争问题)。
2、工作内存中保存了该线程使用到的变量的在主存中的拷贝。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行,不能直接读写主存中的变量。
3、Java内存模型中的工作内存只是个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其它的硬件和编译器优化。
4、内存间的交互操作
lock、unlock
read、load、use、assign、store、write
5、volatile变量
6、原子性、可见性、有序性
7、happens-before原则
2.线程
1.线程的实现方式
三种方式1:1、1:N、N:M。
Java是通过将线程映射到操作系统的线程上去实现的。
2.线程的调度方式
两种方式:协同式线程调度、抢占式线程调度。
Java中使用的是抢占式线程调度。
3.线程的状态转换
1.线程安全和锁优化
线程安全
1)共享数据的种类
1、不可变:不可变对象是值“对象中带有状态的变量都声明为final”。
2、绝对线程安全
3、相对线程安全:Vector、Hashtable等
4、线程兼容:ArrayList、HashMap
5、线程对立
2)线程安全的实现方式
1、互斥同步(Mutual Exclution & Synchronization)
同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个(一些)线程使用。
互斥是实现同步的一种手段,临界区、互斥量、信号量都是互斥的实现方式。
互斥是因,同步是果;互斥是方法,同步是目的。
互斥同步又称为“阻塞同步”,属于一种悲观的并发策略。
2、非阻塞同步
非阻塞同步是一种基于“冲突检测”的乐观并发策略;
即先进行操作,如果“没有其它线程争用共享数据”,那操作就成功了。如果共享数据有争用,产生了冲突,那就再采取其它的补偿措施(比如不断重试,直到成功为止)。
因为这种策略不需要将线程挂起,所以成为非阻塞同步。
“操作和冲突检测”这两个步骤,需要具备原子性,这可以通过硬件的CAS来实现。
CAS指令需要三个操作数:内存位置,旧的预期值,新值。当且仅当“内存位置的值”符合“旧的预期值”时,处理器用“新值”更新“内存位置的值”,否则就不更新。最终无论是否更新,均会返回“旧值”。
3、不须同步
可重入代码:不依赖堆上的数据和共用的系统资源,用到的状态量都由参数中传入,不调用非可重入的方法。如果一个方法,它的返回结果可预测,相同的输入,均能返回相同的输出,它便是可重入的。
线程本地存储:即ThreadLocal,以当前线程哈希码为键,某变量位置的一个键值对。
锁优化
1、自旋锁
因为当线程阻塞,或从阻塞中恢复时,挂起线程和恢复线程的操作都需要转入到内核态去完成,这给系统的性能带来了很大压力。
如果某个锁被占用的时间很短,这时候可以让“后面那个请求锁的线程”稍微等一下,不放弃CPU的执行时间,执行一个忙循环,直到获得锁。
2、锁消除
将一些代码上进行了同步,但实际不会存在共享数据竞争的锁进行消除。
3、锁粗化
如果对一个对象反复加锁和解锁,甚至这个对象在循环体内,则可以把锁加到循环体外,这样可以消除反复加解锁所带来的损耗。
4、轻量级锁
5、偏向锁