JVM
类装载器
根加载器,像 Object 这些类,在一启动的时候,就通过 Bootstrap 启动类加载器加入
到了 jvm ,所以可以直接使用
扩展类加载器,在 jdk 不停的更新途中,加入了很多新的 jar 包,这些 jar 包就使用扩
展类加载器 Extension 加入到了 jvm 中
系统类加载器,自己在 java 项目中创建的 class 类,都会通过 AppClassLoader 系统类
加载器,加载当前应用的 classpath 的所有类进入到 jvm 中
双亲委派
一个类的加载先从根加载器找,根加载器找不到就去扩展类加载器找,扩展类找
不到就去系统类加载器中找。
沙箱安全机制
双亲委派机制就是为了保证沙箱安全机制,就不让人干坏事,不能替换掉 java 本
身自带的一些类
线程私有: native 本地方法
native 是一个关键字
有声明, java 无实现,是使用的 C 语言实现的第三方函数库,
线程私有: PC 寄存器:程序计数器
行号计数器,用来存储执行到了第几行,多线程环境下会出现时间争抢,如果当前
线程时间片用完,那么就需要等待获取到新的时间片来进行执行,那么从等待重新
开始执行的时候,从哪一行开始执行就是用程序计数器来决定的
记录了方法之间的调用和执行情况,类似排版值日表,用来存储指向下一条指令的
地址,也就是将要执行的指令代码,它是当前线程所执行的字节码的行号指示器
线程私有: stack :栈
栈管运行,堆管存储
栈主管 java 程序的运行,在创建线程时创建,在线程结束时栈也释放,栈不存在垃
圾回收问题,只要线程一结束该栈就 Over ,声明周期和线程一致,是线程私有的
8 种基本类型的变量 + 对象的引用变量 + 实例方法都是在函数的栈内存中分配
栈帧钟主要存储三类数据
1 :本地变量,输入参数和输出参数以及方法内的变量
2 :栈操作,记录出栈,入栈的操作
3 :栈帧数据,包括类文件,方法等等 java 方法 = 栈帧
每个方法执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链
接、方法出口等信息,每一个方法从调用直至执行完毕的过程,就对应着一个栈帧
在虚拟机钟入栈到出栈的过程,栈帧的大小和具体 jvm 的实现有关,通常在
256K~756K 之间,约等于 1Mb 左右
java.lang.StackOverFlowError : SOF :把栈撑爆了,是一个 Error 错误
线程共享:方法区
存在垃圾回收
方法区不是专门放方法的地方
供各线程共享的运行时内存区域,它存储了每一个类的结构信息,运行时常量池、
字段和方法数据、构造函数和普通方法的字节码内容
方法区是规范,在不同虚拟机里面实现是不一样的,最典型的就是永久代和元空间
空调 k1 = new 格力()
list list = new Arraylist ()
java 7 以前是:方法区 f = new 永久代
java 8 以后是:方法区 f = new 元空间
所以方法区是一个规范, new 的后面是具体的实现
线程共享: heap :堆
新生代
伊甸区: Eden 元区
new 出来的第一次都存放在这个区域
Eden 区满了会开启 ygc 轻 GC ,执行 ygc 后,大部门对象都会被 kill 掉,没有被
kill 掉的幸存者就会转移到幸存者 0 区
幸存者 0 区: Survivor 0
接收没有被 ygc kill 掉的对象,幸存者 0 区和 1 区会进行交换,保证 0 和 1 两个区
域一个存放活着的对象,一个啥都不存
幸存者 1 区: Survivor 1
被 GC 了 15 次(默认值 15 )没被 kill 掉的会转移到老年代
交换
from 区和 to 区,他们的位置和名分,不是固定的,每次 GC 后会交换,谁空谁
是 to
老年代
如果老年代区域也满了,就会开启 full gc = FGC 重 GC ,如果 full gc 多次,发现
空间没办法腾出来,那么 jvm 就报 OOM 堆内存溢出异常 理论
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾
回收器收集,结束生命。新生区又分为两部分:伊甸区和幸存者区,所有的类都
是在伊甸区被 new 出来的。幸存区有两个: 0 区和 1 区。当伊甸园的空间用完时,
程序又需要创建对象, JVM 的垃圾回收器将对伊甸园区进行垃圾回收 Minor GC
轻 GC ,将伊甸园区中的不再被其它对象所引用的对象进行销毁。然后伊甸园中
的剩余对象移动到幸存者 0 区,如果幸存者 0 区也满了,再对该区进行垃圾回
收,然后移动到 1 区,那如果 1 区也满了呢?再移动到养老区,若养老区也满
了,那么这个时候将产生 MajorGC FULLGC ,进行养老区的内存清理,若养老区
执行了 Full gc 之后依然无法进行对象的保存,就会产生 OOM 异
常 "OutOfMemoryError"
jdk8 以后元空间 /jdk7 之前永久代
堆内存调优
-Xms :设置初始分配大小,默认为物理内存的 1 / 64
-Xmx :最大分配内存,默认为物理内存的 1 / 4
-XX:+PrintGCDetails :输出详情的 GC 处理日志
实践经验
生产环境上的 java 应用, JVM 的 -Xmx 和 -Xms 两个参数的值必须一致,防止内
存忽高忽低产生停顿
java.lang.OutofMemoryError : java heap space :堆内存溢出
GC (分代收集算法)
次数上频繁收集 Young 区
次数上较少收集 Old 区
基本不动元空间
GC 4 大算法
引用计数法
jvm 实现不采用这种方式
有一个计数器去记录每个对象被引用的次数
缺点:较难处理循环引用,有个计数器消耗性能
复制算法
年轻代使用的垃圾回收算法就是复制算法
复制算法的基本思想就是将内存分为两快( Eden 区和 from 区是一快 to 也是
一快),每次只用其中一快,当这一快内存用完,就将还活着的对象复制到
另外一快上面,复制算法不会产生内存碎片 因为 Eden 区对象一般存活率较低,一般的,使用两块 10% 的内存作为空闲和
活动区间,而另外 80% 的内存,则是用来给新建对象分配内存的,一旦发生
GC ,将 10% 的 from 活动区间与另外 80% 中存货的 Eden 对象转移到 10% 的 to 空
闲空间,接下来,将之前 90% 的内存全部释放,以此类推
优点:速度快
缺点:浪费空间 浪费了一半内存,这太要命了
标记清除
老年代使用的就是标记清除与标记整理的混合实现
算法分成标记和清除两个阶段
先标记出要回收的对象
然后统一回收这些对象
缺点:
内存碎片过多
速度没复制算法快,有标记和清除两个操作
优点:
不需要额外的空间
标记压缩
老年代使用的就是标记清除与标记整理的混合实现
也叫标记清除 + 标记整理算法,比标记清除多了一步整理的步骤
缺点:
速度慢
优点:
不会浪费空间和产生内存碎片
JDK9 之后
JAVA 9 的默认垃圾回收器
G1 垃圾回收器
G1 将新生代,老年代的物理空间划分取消了,这样我们再也不用单独的空
间对每个代进行设置了,不用担心每个代内存是否足够。
G1 算法将堆划分为若干个区域(
Region ),它仍然属于分代收集器
https://www.kancloud.cn/luoyoub/jvm-note/2255980
J