CPU寄存器 - 高速缓存 - 主存(RAM)。处理速度 从 左往右 速度 递减
JVM , JMM ,JDK ,JRE
JDK : Java Development Kit 【Java 软件开发工具包 (SDK)】
JRE : Java Runtime Environment
JMM : Java Memory Model
JVM ::Java Virtual Machine
JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,bin里的可以认为就是jvm,lib中则是jvm工作所需要的类库,JVM + lib = JRE
1.JVM的内部结构
中间虚线的是 运行时数据区域 (是一种规范)
每个JVM都有两种机制:
①类装载子系统:装载具有适合名称的类或接口
②执行引擎:负责执行包含在已装载的类或接口中的指令
内存空间 【组成】 :
JVM内存空间包含:方法区、java堆、java栈、本地方法栈。
方法区 是各个线程共享的区域,存放类信息、常量、静态变量。
java堆 也是线程共享的区域,存放类的实例化和数组的。java堆的空间是最大的。如果java堆空间不足了,程序会抛出OutOfMemoryError异常。
方法区 和 堆的区别 :
堆 存放的是 对象 和 数组 ;
方法区 存的是类信息,静态变量,常量,处理逻辑的指令集。
关系就是 : “方法区 相当于 类” “堆 相当于 对象”
方法区 逻辑上 在 堆的 Permanent (永久代 【非堆】)上。物理上 还是在堆上的
JDK1.7 之前 : 字符串常量池 在 方法区 (在堆的永久代)中
JDK1.7:字符串常量池 从 方法区 移到 堆
JDK1.8:字符串常量池 还在方法区 中,但是方法区的实现从 永久代(Perm)变成 元空间(MetaSpace)。
元空间: 本质上与永久代类似,都是JVM规范中方法区的实现。不过 元数据空间并不在虚拟机中,而是使用本地内存。
线程栈 是每个线程私有的区域,用于存储局部变量。它的生命周期线程相同,
本地方法栈 是 为虚拟机的Native方法服务的。
Native方法 :调用非java代码的接口。
使用Native Method的原因 : java应用需要与java外面的环境交互 【比如:与操作系统交互】。
区别:JVM线程栈为 虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
指令计数器,指向当前线程正在执行的字节码指令地址,行号。比如,切换线程后,线程要知道自己原来是在哪里运行的,然后接着运行。
执行引擎 当然就是根据PC寄存器调配的指令顺序,依次执行程序指令。
2.java 堆 的组成结构
Heap = {Old + NEW + Permanent = { Eden , Survivor0, Survivor1 ,Permanent } }
一块是 NEW Generation(新生代), 另一块是Old Generation(老年代). 在New Generation中,有一个叫Eden 的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(Survivor0,Survivor1), 它们用来存放每次垃圾回收后存活下来的对象。在Old Generation中,主要存放应用程序中生命周期长的内存对象【从survivor1 剩下来的内存对象】。
大部分对象在分配时都是在Eden中
较大的对象直接分配到 Old Generation 中
如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。 原因: 很可能是创建了大量的对象。
3.垃圾回收算法
①.Mark-Sweep(标记-清除)算法
首先标记出需要回收的对象,标记完成后统一清除对象。
缺点: 效率不高,而且会产生大量的内存碎片。
②.Copying(复制)算法
将可用内存分为两块,每次只用其中的一块,当这块内存用完以后,将还存活的对象复制到另一块上面,然后再把已经使用的内存空间一次清理掉。
优点:效率高,不会产生内存碎片。
缺点:内存缩小为原来的一半
③.Generational Collection(分代收集)算法 【JVM的垃圾收集器采用的算法】
根据不同代的特点采取最适合的收集算法。
新生代都采取Copying算法:因为大部分的对象的创建【且生命周期短】都在Young区。每次垃圾回收都要回收大部分对象。【将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。】
老年代的特点是每次回收都只回收少量对象,一般使用的是标记清除算法。
4.关于内存调优
原因:过多的GC和Full GC是会占用很多的CPU
目的:减少Full GC次数,减少GC频率,尽量降低CG所导致的应用线程暂停时间
手段:主要是针对内存管理方面的调优,包括控制各个代的大小,GC策略
内存控制:
①.旧生代空间不足
调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和 不要 创建过大的对象及数组 避免直接在旧生代创建对象。
②.Pemanet Generation空间不足
增大Perm Gen空间,避免太多静态对象
③.System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制
④.新生代 设置不宜过大 或过小,新生代占整个堆的1/3比较合适。
5.类加载的过程
过程就是类加载器将 所需的 (.class文件)字节码文件中要执行的代码逻辑以指令的形式 加载到 方法区,调用java方法就通过,java堆,java栈。调用native方法,就通过本地方法栈。最后执行引擎再通过PC寄存器上指令的执行顺序进行执行。
1.1 加载
加载主要是将.class文件中的二进制字节流读入到JVM中。
在加载阶段,JVM需要完成3件事:
1)通过类的全限定名获取该类的二进制字节流;
2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
1.2 连接
①. 验证
验证是连接阶段的第一步,主要确保加载进来的字节流符合JVM规范。
②.准备
为静态变量在方法区分配内存,并设置默认初始值
③.解析
虚拟机将常量池内的符号引用替换为直接引用的过程
④.初始化
类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值
小知识:类加载器:类加载器实现的功能是为加载阶段获取二进制字节流的时候。
以上为 :双亲委派模型。
概念:如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器。 只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。
类加载器的作用:将.class文件加载到jvm的内存空间中。
双亲委派模式的代码实现:
1)首先检查类是否被加载,没有则调用父类加载器的loadClass()方法;
2)若父类加载器为空,则默认使用启动类加载器作为父加载器;
3)若父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的findClass() 方法。
面试题:自己写的java.lang.String类能否被加载?
根据上边的原则 1), 我们自己写的String应该是被Bootstrap ClassLoader(顶层类加载器)加载了,所以App ClassLoader就不会再去加载我们写的String类了,导致我们写的String类是没有被加载的。
6.JMM :JAVA 内存模型 ,JVM : Java虚拟机模型。
JMM:包括 本地内存 (线程),主内存 。
本地内存 存放 着 主内存的共享变量的副本。线程在 本地内存 中运算后,将结果刷到主内存。如果线程A 与 线程B 都同时操作,这就是多线程问题的由来了。
Java 内存模型 【JMM】的抽象图:
JVM :包括方法区,堆,虚拟机栈,本地方法栈,PC寄存器。
线程共享 : 方法区,堆
线程私有:虚拟机栈(执行java方法),本地方法栈(执行native方法),PC寄存器(指令执行顺序)
7.AtomicInteger 提供原子操作的Integer类,不会有线程安全问题。十分适合高并发情况下。
保证 原子性 的秘诀 :基于CAS【比较交换】
CAS的思想很简单:主内存值 M,期望值 V,待更新值 U,Only M =V ,update V 去主内存,M≠V ,则继续对V+ 1,U+ 1,再去取主内存值M,直到V=M ,才将更新值进行更新
假设线程1和线程2通过getIntVolatile拿到value的值都为1,线程1被挂起,线程2继续执行
线程2在compareAndSwapInt操作中由于预期值和内存值都为1,因此成功将内存值更新为2
线程1继续执行,在compareAndSwapInt操作中,预期值是1,而当前的内存值为2,CAS操作失败,什么都不做,返回false
线程1重新通过getIntVolatile拿到最新的内存value为2,再进行一次compareAndSwapInt操作,这次操作成功,因为取了最新的值做为预期值,预期值是2,内存值也是2,加1更新后内存值更新为3
8.线程池:为了避免创建和销毁进程,对内存的开销,用线程池,给定数量的线程,当有请求来到,直接从线程池请求,分配线程执行请求的任务。