JVM3:内存分配、回收策略、内存模型JMM

27 篇文章 0 订阅

垃圾回收算法与垃圾回收器
内存分配

  • 对象优先在Eden区,若Eden区没有足够的空间进行分配时,会触发一次MinorGC
  • 大对象(需要大量连续空间的Java对象,比如很长的字符串或者数组)直接进入老年代,因为大对象需要内存分配大量连续空间,因此大对象可能会让虚拟机提前触发GC来获取足够大的空间放置大对象。虚拟机提供了一个-XX:PretenureSizeThreshold参数,让大于这个设置值的对象直接在老年代分配。这样做的目的是避免Eden区以及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存) 。
  • 长期存活对象进入老年代。虚拟机采用分代收集算法,所以要对对象进行分类,设置了一个计数器,当这个对象在Eden区产生后经历一次MinorGC仍然存活的话,并且可以被Survivor区容纳的话,就把他放进Survivor区并且计数器+1,每经过一次Minor并存活,计数器+1,默认计数器达到15,对象就进入老年代。
  • 动态年龄判定:为了更好的适应不同程序的内存状况,当Survivor区中相同年龄计数器大小的对象超过Survivor区中所有对象的一半,那么就将等于和大于相同年龄的对象放进老年代。
  • 空间分配担保:在进行Minor GC之前,会检查老年代连续空间大小是否大于新生代所有对象的空间大小,如果大于,说明本次MinorGC是安全的;如果小于,虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,为true则继续检查老年代连续空间大小是否大于历年晋升到老年代的对象的平均大小。如果大于,继续进行一次Minor GC,但是这次GC是有风险的;如果小于或者等于false,就进行一次Full GC。

JVM是整个计算机的虚拟模型,JMM属于JVM
Java的内存模型:基于线程的内存模型,简称JMM
JMM 定义的主要目标:为了定义程序中各个变量的访问规则,也就是如何将变量从内存中取出来,怎样将它写入。定义了虚拟机(JVM)在计算机内存中(RAM)的工作方式。这里的变量是实例字段、静态字段和数组元素(线程共享)

一、主内存与工作内存。主内存也就是所有内存共享区域(堆、方法区、运行时常量池);工作内存就是每个线程独享的区域。JMM规定所有变量都必须存储到主内存中,每条线程都有自己的工作内存,其中保存了该线程使用到的变量的主内存副本(也就是说工作内存中使用到的变量是主内存中变量的一个副本) 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接读写主内存变量,不同线程之间也无法直接访问彼此的工作内存变量。

二、内存间交互操作(一个变量怎么从主内存拷贝到工作内存,怎么从工作内存同步回主内存)
I.作用于主内存的变量:
1.lock(把一个变量标识为一条线程独占的状态)
2.unlock(把一个处于锁定状态的变量释放出来,只有释放了才能被其他线程锁定)
3.read(把一个变量的值从主内存传输到线程的工作内存中)
4.write(把store操作从工作内存中拿到的变量的值放入到主内存的变量中)
作用于工作内存的变量:
1.load 载入(把read操作从主内存中中得到的变量值放入工作内存的变量副本中)
2.use 使用(把工作内存中的一个变量值传递给执行引擎)
3.assign 赋值(把从执行引擎接收到的值赋值给工作内存的变量)
4.store 存储(把工作内存中一个变量的值传送到主内存中,便于write使用)
II.Java内存模型的三大特征:
1.原子性 :一个操作是不可中断的,即便是在多个线程一起执行的时候也,一个操作一旦开始,就不会被其他线程干扰。
基本数据类型的访问读写是具有原子性的,如:int i=0;和i=3;都是原子操作;
i++就不是原子操作了,展开i=i+1,是两步操作。如果需要大范围的原子性,就需要内建锁或lock体系来支持。
2.可见性:一个线程修改了共享变量的值,其他的线程也能立即得知此修改。volatile、synchronized、final三个关键字可以实现。
3.有序性:在本线程内观察,所有的操作都是有序的;在线程内观察另一个线程,所有的操作都是无序的。

Java内存模型有时候不需要任何手段就能保证到有序性,也叫happens-before原则。
如果两个操作数无法从happen-before原则推导出来,就不能保证他们的有序性,虚拟机可以随意对他们进行重排序。
先行发生原则包括:程序次序规则、锁定规则、volatile变量规则、传递规则、线程启动\中断\终结规则、对象终结规则。
要想并发程序的正确执行,必须同时保证原子性、可见性和有序性。
三、volatile变量的特殊规则

volatile是类型修饰符,保证在多线程中,写操作先行与读操作。
I.保证了此变量对所有线程的可见性和有序性,volatile变量在各个线程中是一致的,但无法保证原子性,所以在并发环境下,volatile变量是不安全的。
volatile保证可见性的原理:缓存一致性协议。
volatile保证有序性:(禁止处理器重排序)内存屏障
在不符合(1.运算结果并不依赖变量当前值,或者能够确保的只有单一线程修改变量的值。2.变量不需要与其他状态变量共同参与不变约束。)
这两条规则的情况下,我们仍然需要添加内建锁或lock来保证原子性。
并发场景下堆类似i++操作如何保证程序运行出正确结果???1、加锁2、使用原子类(java.util.atomic包下所有类内部使用了CAS操作来保证原子性)
II.禁止指令重排。
1.当程序执行到volatile变量的读操作或写操作时,前面的操作一定全部执行了,后面的操作一定没有执行,并且结果已经对后面的操作可见。
2.在指令优化是,不能将在对volatile变量访问的语句放在其后面执行,也不能将volatile后面的语句放在他前面执行。
volatile只是一个变量修饰符,不是锁,所以不能保证原子性。

JVM OOM和内存泄漏问题
OOM一般分为三种:堆区内存溢出、栈区内存溢出、方法区内存溢出
堆区内存溢出主要原因是创建了太多的对象。
栈区内存溢出主要与栈空间和线程有关,因为栈是线程私有的,如果创建太多的线程,内存值超过站空间的上限,就会产生OOM
方法区内存溢出主要是由于动态加载类的数量太多,或者是不断创建一个动态代理,用不了多久方法区内存也会溢出,产生OOM,而这里是JDK1.7之前会报permgem OOM,1.8会报metaspace OOM;这是因为1.8中删除了堆中的永久代,转而使用元数据区。
内存泄漏一般是因为对象被引用无法回收。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值