学习之路: https://github.com/hollischuang/toBeTopJavaer.
链接: https://www.jd.ma/.
1.内存结构图
(1)如何判断哪些是可回收垃圾?
答:<1>引用计数法,但是没有没使用,因为无法解决对象之间相互循环引用的问题,也就是栈帧的局部变量表没有引用对象,但是堆里面的对象又相互引用。
<2> 可达性分析算法:将“GC Roots”对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的都是垃圾对象。
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等。
finalize():可达性分析算法找出要gc的对象后,会通过该方法再次标记,若对象在此方法中关联上别的引用,那么不会被回收。
(2)如何判断一个类是无用的类?
答:<1>该类所有的实例都被回收,也就是堆中不存在该类的任何实例。
<2>加载该类的classLoader已经被回收。
<3>该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
2.类加载过程
解析:可以通过javap -v(-c) Math.class > Math.txt 反编译成字节码文件,看看执行流程,会展示引用的操作方式。
3.类加载器
(1)自定义类加载器:继承 classLoader 类,通过调用父类的 loadClass() 方法重写 findClass() 方法。
4.双亲委派原则:子类的类加载器不会先去加载类,而是交给父类去加载,如果父类找不到对应的类,会一层一层的返回,直到所有的父类都不能加载,只能自己去加载。
Custom ClassLoader -> Application ClassLoader -> Extersion ClassLoader -> Bootstrap ClassLoader
源码:(当前类加载器先看自己是否已经加载了此类,没有就委托给父类加载,当委托给最后一个 Bootstrap ClassLoader 类加载器时,就会走 findBootstrapClassOrNull() 方法,是一个native类型的方法,底层由C语言实现)
5.虚拟机栈(内存栈)
(1)栈:线程独有 ,会为每一个线程分配一个栈内存空间。
(2)栈内存:可以通过VM options = -Xss128k配置内存大小,默认是1M。
(3)栈帧:存储每一个方法。
<1>局部变量表:存储这个方法的局部变量名和对应的数值的地方,当时一个对象时,则存储对象和对应在堆内存中的地址。
<2>操作数栈:找到对应的常量值进入操作数栈,进行加减等四则运算,当需要赋值给指定的变量时,从操作数栈弹出,给变量赋值后存储到局部变量表中。
<3>动态链接:将符号引用转换为直接内存地址的一个过程,和静态链接一致,只不过静态链接在类加载就完成了。
<4>方法出口:方法(return)返回数据的位置,返回到哪里。
(4)如果方法里面有实例化的对象,这个对象会存到堆里,但是在栈帧的局部变量表会存该对象对应在堆里面的引用。
例:主方法main() 先执行,先进栈,然后执行公有方法computer() ,后进栈,先进后出。
6.程序计数器
(1)记录线程运行的字节码文件的行号。
(2)通过执行引擎来修改计数器的值,从而推进线程的运行。
7.方法区(元空间)
(1)方法区用的是直接内存,而不是分配给jvm的内存。
(2)存储常量,静态变量,类元信息(类型信息,类型的常量池,字段信息,方法信息,类变量,指向类加载器的引用,指向class实例的引用,方法表)
8.本地方法栈
(1)native修饰的方法,底层不是java实现的,或是c语言写的,类似的方法有Thread类里面的start0()底层方法。
9.堆
(1)新创建的对象首先存到Eden区,若放满后,jvm会操控执行引擎创建垃圾回收线程,minor gc。
10.垃圾回收机制
(1)Minor GC/Young GC :频繁,新生代。
(2)Major GC/Full GC:老年代,新生代,方法区的垃圾,速度一般比Minor GC 慢10倍。
(3)当一个对象经历了15次minor gc后,还没有被清理掉的情况下,会被放入老年代,如果是大对象(对象动态年龄判断机制是判断该批对象的大小大于survivor区的50% ),导致eden放不下的情况下,也会被直接放入老年代。
(4)当老年代放满了之后,会执行full gc,而当full gc之后还是满的时候,就会内存溢出。
(5)调优的目的是为了减少gc,核心是减少full gc,引入一个概念STW(Stop The World),当执行stw时,会暂停用户线程,若没有这个暂停的机制,那么用户线程和gc线程一起执行,用户线程执行完毕,要被销毁了,但是gc在它销毁之前将它标记为了非回收线程,那么就会有冲突。
(6)-Xms10m -Xms10m 堆, -Xss10m 栈。
11.垃圾回收算法
(1)Mark-Sweep(标记清除):位置不连续,容易产生碎片,可能导致每一个空间放不下新的对象。
(2)Copying(复制算法):分成两个大小相同的内存空间,大当其中一块满了之后,会把存活的对象全部移到另一块上,然后将满的那块全部清理掉,比较浪费空间,没有碎片。
(3)Mark-Compact(标记整理):没有碎片,效率偏低。
12.垃圾收集器
常用:
(1)Serial(新生代):新生代采取复制算法,暂停所有用户线程,但只会有一个 gc 收集线程。
(2)Serial Old(老年代):老年代采取标记-整理算法,暂停所有用户线程。
(3)Parallel Scavenge(新生代):java1.8默认的算法,会开启多个 gc 收集线程,采用复制算法,暂停所有用户线程。
(4)Parallel Old(老年代):标记-整理算法,暂停所有用户线程。
(5)ParNew(新生代):Parallel Scavenge基础上的优化,采用复制算法,暂停所有用户线程。
(6)CMS(ConcurrentMarkSweep老年代):标记-清除算法,会产生碎片,漏标,浮动垃圾,若并发清理过程中,产生新的垃圾,但是放不下,会立即终止CMS收集,交给Serial Old来进行gc。
先初始标记(会暂停用户线程,不过很短)->并发标记(可能会引起错标)->重新标记(会暂停用户线程,不过很短)->并发清理。运行大概在100-200毫秒。
PS:错标,初始标记的时候被标记为可回收垃圾,但是在并发标记的时候,被重新引用到了,导致不可回收,可以通过重新标记改变。
不常用(没有分代的概念,gc效率更高):
(7)G1(Garbage-First使用复制算法):是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以及高概率满足GC停顿时间要求的同事,还具备高吞吐量性能特征。
初始标记->并发标记->最终标记->筛选回收(筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用 户所期望的GC停顿时间来制定回收计划)。
Eden、Survivor、Old、Humongous(大对象,而不是直接存入老年代,在Full gc时进行回收)。运行大概不会超过10毫秒。
(8)ZGC(JAVA11默认):运行大概不会超过1毫秒。
(9)Shenandoah(G1的增强版,redhat开发):
(10)Epsilon:debug调试的时候会用到,不会在项目部署时用到。
12.监控工具
(1)jvisualvm(java自带) :Ctrl + R 命令窗口输入 jvisualvm 弹出可视化监控界面。
(2)Arthas(阿里) :启动后使用dashboard查看实时的一个监控窗口,输入thread {id},id为每一个线程的id,即可查看对应线程的详情信息。
13.jvm三种运行模式
(1)解释模式:只使用解释器,执行一行jvm字节码就编译一行为机器码。
(2)编译模式:只使用编译器,先将所有jvm字节码一次性编译成机器码,然后一次性执行机器码。
(3)混合模糊:依然使用解释模式执行代码,但是对于一些"热点"代码采用编译模式执行,jvm一般采用混合模式执行代码。
14.对象逃逸分析(java1.7之后默认开启)
就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中。如果新实例化的对象只在本方法中使用,那么该对象存到栈帧里面,随着方法出栈一起销毁,如果不够存,那么也会存到堆里面。
15.引用类型
(1)强引用:普通变量引用。
(2)软引用:将对象用SoftReference软引用类型的对象包裹,正常情况下不会被回收,但是GC后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉软引用可用来实现对内存敏感度不高的高速缓存。(应用场景:浏览器回退)
// 软引用
pubilc static SoftReference<User> user = new SoftReference<User>(new User());
(3)弱引用:WeakReference包裹,和没引用差不多,只要gc就会回收。
(4)虚引用:最弱的一种引用关系,几乎不用。
学习于昭君老师和狂神老师(UP主)。