本文共 7228 字,大约阅读时间需要 24 分钟。
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.
可见性(通知机制)
volatile关键字
class Juc02 { volatile int i = 10; public void addTo1205() { this.i = 1205;}}/** * JMM可见性 */public class Juc01 { public static void main(String[] args) { Juc02 juc02 = new Juc02(); new Thread(() ->{ System.out.println("我进入了Tread线程"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } juc02.addTo1205();//将10-->1205 System.out.println(Thread.currentThread().getName()+"\t更新数,数值为: "+juc02.i); },"AAA").start(); while(juc02.i ==10){ //System.out.println("我通知了main线程"); } System.out.println(Thread.currentThread().getName()+" mission is over"); }}
原子性
有序性
计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,分为以下3中
1.单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致.
处理器在进行重新排序是必须要考虑指令之间的数据依赖性 2.多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否保持一致性是无法确定的,结果无法预测
cpu|>内存>硬盘 三级缓存
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在 主内存,主内存是共享内存区域,所有线程都可访问, 但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:
JVM是运行在操作系统之上的,它与硬件没有直接的交互
虚拟机自带的加载器
用户自定义加载器
Java.lang.ClassLoader的子类,用户可以定制类的加载方式双亲委派
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。
沙箱机制
执行引擎负责解释命令,提交操作系统执行。
Native Interface本地接口
Native Method Stack
new thread().start()什么时候启动? 不确定,OS启动---native save0()
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码)
不会发生内存溢出(OutOfMemory=OOM)错误8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配。
方法调用自己时:`java.lang.StackOverflowError`
6.1 栈存储什么?
栈帧中主要保存3 类数据: 本地变量(Local Variables):输入参数和输出参数以及方法内的变量; 栈操作(Operand Stack):记录出栈、入栈的操作; 栈帧数据(Frame Data):包括类文件、方法等等。
供各线程共享的运行时内存区域。它存储了每一个类的结构信息,例如模板静态的东西,运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容。上面讲的是规范,在不同虚拟机里头实现是不一样的,最典型的就是永久代(PermGen space)和元空间(Metaspace)。
But 实例变量存在堆内存中,和方法区(静态变量)无关静态>构造块>构造方法
一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为三部分:
Young Generation Space | 新生区 | Young/New |
---|---|---|
Tenure generation space | 养老区 | Old/ Tenure |
Permanent Space | 永久区 | Perm |
堆内存逻辑上分为三部分:新生+养老+永久
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分: 伊甸区(Eden space)和幸存者区(Survivor pace) ,所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存 0区。若幸存 0区也满了,再对该区进行垃圾回收,然后移动到 1 区。那如果1 区也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生MajorGC(FullGC),进行养老区的内存清理。若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。
如果出现java.lang.OutOfMemoryError
: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:
MinorGC的过程(复制->清空->互换)
1:eden、SurvivorFrom 复制到 SurvivorTo,年龄+1
首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+12:清空 eden、SurvivorFrom
然后,清空Eden和SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to3:SurvivorTo和 SurvivorFrom 互换
最后,SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区。部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
JVM垃圾收集(Java Garbage Collection )
调新生代和养老代 无法修改永久代在Java8中,永久代已经被移除,被一个称为元空间的区域所取代。元空间的本质和永久代类似。
元空间与永久代之间最大的区别在于:
永久代使用的JVM的堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。
内存调优:物理上堆内存–>在Runconf/VM改参数
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
public static void main(String[] args){ //返回 Java 虚拟机试图使用的最大内存量。long maxMemory = Runtime.getRuntime().maxMemory() ;//返回 Java 虚拟机中的内存总量。long totalMemory = Runtime.getRuntime().totalMemory() ;System.out.println("MAX_MEMORY = " + maxMemory + "(字节)、" + (maxMemory / (double)1024 / 1024) + "MB");System.out.println("TOTAL_MEMORY = " + totalMemory + "(字节)、" + (totalMemory / (double)1024 / 1024) + "MB");}
JVM 内存不够用了,没有空闲内存,并且垃圾收集器也无法提供更多内存。
1.死循环会发生oom
String str = "comeOOM" ;while(true) { str += str + new Random().nextInt(88888888) + new Random().nextInt(999999999) ;}
2.参数设置过小会发生oom
-Xms8m -Xmx8m -XX:+PrintGCDetails
1.JVM内存模型以及分区,需要详细到每个区放什么
2.堆里面的分区:Eden,survival from to,老年代,各自的特点。 3.GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方 4.Minor GC与Full GC分别在什么时候发生
垃圾收集(分代收集算法)
当新生区或者养老区快满时发生GC因此GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC)
Minor GC和Full GC的区别
老年代一般是由标记清除或者是标记清除与标记整理的混合实现
JVM中的内存是怎么划分的
说一下对象创建过程中的内存分配
对象被访问的时候是怎么被找到的
内存分配与垃圾回收
JVM如何判定一个对象是否应该被回收
//1.引用计数算法2.可达性分析(Reachability Analysis)算法JVM垃圾回收算法有哪些?
JVM的垃圾收集器? (重点CMS收集器)
JVM常用内存调优命令
JDK8中在内存管理上的变化
Java中的类加载机制有了解吗
转载地址:http://kvxab.baihongyu.com/