Fork me on GitHub

这篇为读<<深入理解JAVA虚拟机>>笔记

Java内存区域与内存溢出异常

Java内存数据区域

  1. 虚拟机栈:为线程私有,在方法执行的同时,会创建一个栈帧,用于存放局部变量表,方法的出口等信息,每个方法从调用开始至执行结束,意味着一个栈帧在虚拟机栈中入栈到出栈的过程,当线程请求的栈深度大于虚拟机允许的深度会抛StackOverFlowError.当栈拓展到无法获取足够内存时,会抛OutOfMenoryError
  2. 本地栈: 通虚拟机栈一样,为线程私有.两者区别在于虚拟机栈用于执行Java方法,本地栈为native服务
  3. 程序计数器: 为线程私有,通过计数器来选择下一步的执行指令,分支,循环,跳转,异常处理,线程恢复等.注意,执行Java方法时记录的是虚拟机栈的地址,如果执行Native方法,值为空

  4. 堆(Java Heap) Java Heap被所有线程共享的区域 是Java虚拟机管理内存中最大的一块,用于存放所有创建的的对象实例,是java GC回收的主要区域

  5. 方法区(Method Area) 通堆一样,被所有线程共享,用于存放常量,类名,修饰符,方法描述等
  6. 常量池: 为方法区一部分,主要存放编译器生成的各种常量与符号引用.同方法区一样,会抛出OutOfMemoryError

  7. 直接内存: 直接内存不是Java虚拟机运行期的一部分.Java1.4加入NIO类,它可以使用Native函数直接分配堆外内存,通过堆中DirectByteBuffer对象作为这块对象的引用,可以在一些场景中显著提升性能. 直接内存不受虚拟机内存限制,但是会受系统控制,也有可能OutOfMemoryError

java虚拟栈内存中只有计数器区域不会出现OutOfMemoryError情况

垃圾收集器与内存分配策略

Java四种引用类型

  1. 强引用 StrongReference new Object();当强引用存在,垃圾收集器永远不会进行回收强引用类型对象
  2. 软引用 SoftReference 有用但非必须的对象,当内存不足时,会将这类对象进行回收
  3. 弱引用 WeakReference 同软引用类似,有用但非必须对象,不同点在于,垃圾收集器工作时,无论内存是否存在,都会被回收
  4. 虚引用 PhantomReference 关系最弱,一个对象是否有虚引用的存在,对其生存时间没影响,也无法通过虚引用返回实例,只是能在对象在垃圾回收收到通知

回收方法区

永久代主要回收两种类型:废弃常量和无用的类
废弃常量判断依据为:当前系统没有任何对象引用的常量
无用的类判断依据比较复杂,符合三个条件才满足

  1. 该类所有实例被回收,及堆中不存在任何该类的实例
  2. 加载该类的ClassLoader被回收
  3. 该类的Class对象没有被任何地方调用,在任何地方都不能通过反射访问该类方法

经过可达性算法标记为不可达的对象,也不会立即”死亡”.要宣判一个对象死亡,至少需要两次标记.经过可达性分析发现没有与GC Roots相连接的调用链,第一次标志并进行一次筛选,条件为是否有必要执行finalize()方法.对象没有覆盖finalize()或者方法已经被系统调用过,虚拟机将这两种情况标记为”没必要执行”

当对象被标记为有必要执行,对象会放入F-Queue队列中,并稍后由虚拟机创建的线程执行.gc会将F-Queue进行另一次标记,在finalize()方法中,如果对象与引用链任一重新建立联系,则会被提出”即将回收”集合

内存分配及回收策略

  1. 对象优先分配到Eden
  2. 大对象直接进入老年代 通过-XX:PretenureSizeThreshold 设置大小
  3. 长期存活的对象进入老年代,默认为15 参数为- XX:MaxTenuringThreshold=15
  4. 动态年龄判定.虚拟机不是永远要求对象年龄大于MaxTenuringThreshold才会进入老年代,如果survivor中相同年龄的对象所占内存大于survivor空间一般,那么所有该年龄的对象进入老年代
  5. 空间分配担保
    在新生代进行Minor GC时,如果老年代可使用连续内存大于新生代,没有问题;如果小于,查看HandlePromotionFailure设置值是否允许担保失败;如果为ture,查看老年代最大可用空间是否大于以往新生代晋升老年代对象平均大小,如果大于,进行MinorGC,如果小于或者HandlePromotionFailure为否,直接进行FullGC

取平均值是一种概率,如果某次minorGC 对象大量增加,则导致担保失败,失败后只能重新发起一次Full GC
大概意思如下

1
2
3
4
5
6
7
8
9
10
11
12
13
if(老年代最大连续空间<新生代空间){
if(HandlePromotionFailure&&老年代空间>以往晋升的平均值){
try(){
Minor GC
}catch (){
Full GC
}

}else{
Full GC
}

}

Java内存模型

主内存与工作内存

Java内存模型主要目标为虚拟机访问各种变量的规则,即虚拟机读取变量与向内存中存储的底层实现.此处变量与编码程序定义变量不同,是指对象的成员变量,静态变量,不包括局部变量和方法参数,这两个存在虚拟机栈为线程私有,不会共享

  1. 工作内存中变量都是主内存对象的拷贝
  2. 不同线程不能访问对方工作内存的变量,变量在线程中的传递需要通过主内存
  3. 对变量的所有操作只能在工作内存中,不能直接在主内存操作

内存模型与JVM内存结构不是一个层次的划分,两者基本没有联系,根据主内存,工作内存,变量的定义来看
主内存对应JVM 堆中部分内存,及存放对象实例
工作内存对应虚拟机栈部分数据

内存间交换

  1. lock 作用主内存变量,标记成线程独占
  2. unlock 作用于主内存变量 解除锁定标记,解除后后其他线程才能继续使用
  3. read 作用于主内存变量,从主内存传输到线程的工作内存
  4. load 作用于工作区变量,将从主内存read的变量复制到工作区
  5. use 作用于工作区变量,将工作区变量传递给执行引擎,虚拟机遇到需要使用变量的值时执行此操作
  6. assign 作用于工作变量,赋值,虚拟机遇到赋值的操作执行此操作
  7. store 工作于主内存变量,将工作内存保存到主内存
  8. write 工作于主内存变量 ,写入到主内存

8种操作必须满足以下原则

volatile 关键字

volatile 有两个特点

  1. 禁止指令重排序优化
  2. 保持volatil修饰的变量对所有线程可见性。volatile修饰的变量在值更新后立即刷新至主内存,使用volatile修饰的变量需要从主内存加载值

volatile没有实现原子性,在以下场景需要进行加锁来保证原子性

  1. 运算结果不依赖当前变量值,或者只有单一线程改值
  2. 变量不需要与其他的状态变量共同参与不变约束

原子性,可见性,有序性在Java内存模型体现

原子性: 通过synchronized 实现
可见性: volatile,final,synchronized 实现 volatile 要求从主内存取值,final,synchronized 通过”一个对象被unlock,必须先把值同步到主内存”
有序性: volatile ,synchonized volatile禁止重排序,synchronized 通过”一个变量一个时刻只允许一条线程锁定”原则实现

先行发生原则

先行发生原则指如果两个操作符合先行并发原则,操作A先行发生于操作B,那操作B一定能被操作A影响到
如果两个操作符合下面关系则不会重排序

1
程序次序规则、管程锁定规则、volatile变量、线程启动规则、线程终止规则、线程中断规则、对象终结规则、传递性

先行发生原则 约束指令重排序,保证同步

显示 Gitment 评论