1.电脑内存结构
- cpu
- 物理内存
- 寄存器:存储计算的中间结果
- 消息总线:决定了一次获取多少bit的数据,决定了最大寻址空间(可以有多大的内存空间),32位有4g的内存空间
2.物理内存的分配
- 系统空间:操作系统所用的运行空间
- 用户空间:应用程序申请的空间,进程的地址空间不重复
- 进程不活动时,会将物理内存转移到磁盘上,进程唤醒时从磁盘重新加载
- 物理内存不够用时也会触发磁盘持久化,因此当swap分区活跃度过高时则内存不足
- 访问硬件一般通过系统空间,用户程序一般不允许直接访问硬件资源,叫做系统调用
- 网络传输的数据先经过系统空间,再复制到用户空间
3.jvm运行在用户空间,包括
- 运行时数据区
- 执行引擎:执行方法中的指令,分为即时编译器,垃圾收集器
- 类加载器: 负责加载 .class文件
- 本地库接口:native Memory不同的编程语言,内存独立的标记为native,可以通过执行引擎调用
- 本地方法库:
4.运行时数据区
- 方法区(线程共享):
- hotspot又可叫做永久代。
- java8之后取消方法区
- 用于存储虚拟机加载的:静态变量+类信息+运行时常量池(类信息:类的版本、字段、方法、接口、构造函数等描述信息 )
- 类信息转移到metaspace,运行时常量池和静态变量转移到堆
- 垃圾收集器主要负责运行时常量池的收集与类的卸载
- 方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常
- java8 中 永久代参数 (PermSize MaxPermSize) -> 元空间参数(MetaspaceSize MaxMetaspaceSize)
- 堆(线程共享)
- 对象实例以及数组
- 在虚拟机启动时创建
- 结构:新生代(Eden区+2个Survivor区) 老年代 永久代(HotSpot有)
- 栈
- 每个方法被执行的时候 都会创建一个“栈帧”
- 栈帧包含局部变量表(包括参数)、操作符号栈、方法出口等信息。
- 局部变量表存储 基本数据类型(
boolean
、byte
、char
、short
、int
、float
、long
、double
)、对象引用(引用指针,并非对象本身) - 64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个
- 本地方法栈:线程私有,Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies
- 程序计数器:线程独立拥有,指向下一个将要执行的代码,内存占用小,可以忽略不记
- 局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的
- 栈的生命期是跟随线程的生命期
4.java程序的执行流程:编译,解释,运行
5.java7之前jvm时sun的hotspot,8之后时hotspot和jrockit的结合
6.复制算法
算法过程:
1. Eden+S0可分配新生对象;
2. 对Eden+S0进行垃圾收集,存活对象复制到S1。清理Eden+S0。一次新生代GC结束。
3. Eden+S1可分配新生对象;
4. 对Eden+S1进行垃圾收集,存活对象复制到S0。清理Eden+S1。二次新生代GC结束。
5. goto 1。
默认Eden:S0:S1=8:1:1
7.垃圾回收算法
- 引用计数法:
- 其原理是:给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器加1,当引用失效时,计数器减1,当计数器值为0时表示该对象不再被使用。需要注意的是:引用计数法很难解决对象之间相互循环引用的问题
- 强引用
- 在程序代码中普遍存在的,类似
Object obj = new Object()
这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。 - 软引用
- 用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
- 弱引用
- 也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。比如 threadlocal
- 虚引用
- 也叫幽灵引用或幻影引用(名字真会取,很魔幻的样子),是最弱的一种引用 关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。
- 其原理是:给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器加1,当引用失效时,计数器减1,当计数器值为0时表示该对象不再被使用。需要注意的是:引用计数法很难解决对象之间相互循环引用的问题
- 可达性分析算法
- 从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。
- GC Roots的对象包括下面几种:
-
a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
b) 方法区中类静态属性引用的对象;
c) 方法区中常量引用的对象;
d) 本地方法栈中JNI(Native方法)引用的对象。
-
- GC Roots的对象包括下面几种:
- 从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。
- 标记-清除算法
- 标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间
- 容易产生内存碎片
- 标记-整理算法
- 该算法标记,之后它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存
- 复制算法
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题,将存活的复制到另一侧
- 内存空间的使用做出了高昂的代价
- 分代收集算法
- 核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收
- 年轻代(Young Generation)的回收算法 (回收主要以Copying为主),当Eden没有足够空间的时候就会 触发jvm发起一次Minor GC
- 年老代(Old Generation)的回收算法(回收主要以Mark-Compact为主)
8.垃圾收集器
- Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC
来强制指定。 - Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本。 - ParNew收集器(停止-复制算法)
新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。 - Parallel Scavenge收集器(停止-复制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC
来强制指定,用-XX:ParallelGCThreads=4
来指定线程数。 - Parallel Old收集器(停止-复制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。 - CMS(Concurrent Mark Sweep)收集器(标记-清理算法)
高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。 - https://www.cnblogs.com/aspirant/p/8663911.html cms
- https://www.cnblogs.com/aspirant/p/8663897.html cms与给g1
- https://www.cnblogs.com/aspirant/p/8663872.html g1
9.GC是什么时候触发的
- GC有两种类型:Scavenge GC和Full GC。
- Scavenge GC 一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
- Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:
a) 年老代(Tenured)被写满;
b) 持久代(Perm)被写满;
c) System.gc()被显示调用;
d) 上一次GC之后Heap的各域分配策略动态变化;