java源代码对应java source
javap将源代码编译成java class字节码(支持跨平台,可以被虚拟机解释为使用于各个平台的机器码)
接下来图中所剩下的部分都可以称为java虚拟机的一部分。使用java命令执行字节码后,就会创建出java虚拟机。
生成main主线程执行方法,此线程需要的内存由虚拟机分配(所有创建的线程所需的内存都是来自于虚拟机栈)。
主线程碰到一个没见过的类Main,把Main这个类通过类加载子系统把类的原始信息加载到方法区(把字节码文件读取到内存里来)。
接下来开始执行main方法,又碰到一个没见过的类Student,把Student的类信息加载到方法区。(student不是成员变量,而是方法局部变量,所以上面Main类加载的时候是没有加载到方法区的)。
遇到一个new,计算对象创建所需要的内存空间,创建对象然后放到堆中的空闲处。
stu这个局部变量跟方法参数args,它们都是引用地址,引用对象的内存都是放在虚拟机栈中。
方法分为两类,一类是java可以实现的,比如上面的study方法。另外一种是需要调用操作系统底层函数实现的比如hashcode方法。java方法内存用的虚拟机栈,本地方法用的本地方法栈。
main线程代码执行到一半,可能要交出cpu使用权,那下次再回来时怎么知道代码从哪里继续?这就需要用到程序计数器,记录当前代码执行到哪行代码。
stu=null,没有对象引用的对象可以进行回收
机器并不认识字节码,这就需要通过解释器把机器码翻译为机器码
调用比较频繁的代码称为热点代码,就不适合用解释器解释,会通过即时编译器翻译为机器码并缓存。
方法区是规范。永久代和元空间是不同版本的虚拟机对方法区的实现
类的基本信息存储在方法区(类加载时放入),同时也会在堆内存创建出一个X.Class对象。我们访问类的原始信息不能直接访问,需要通过X.Class对象间接访问。
什么时候方法区里类的信息内存会得到清理呢?如上图,虽然Y.Class对象和c对象都没有被引用了,但是方法区中类Y不能被卸载。只有整个类加载器不再使用了,那这个类加载器所加载的类才会被卸载。
堆内存参数
-Xmx 最大堆内存
-Xms 最小堆内存
-Xmn 新生代内存
-XX:SurvivorRatio=3 eden:from=3:1
from所占内存=to所占内存
Survivor内存=from内存+to内存=2G
标记不能被垃圾回收的对象,垃圾回收发生时标记的对象把它保留下来,未标记的就清理掉。
1、标记清除
GC Root:局部变量引用的对象、静态变量引用的对象
缺点:内存碎片
2、标记整理
无内存碎片,但是效率低
3、标记复制
效率相对于标记整理更好。因为清除较快,直接把幸存对象复制到另一片区域,原区域作废。(适合新生代,因为新生代存活对象较少,复制的过程很快,而老年代存活的对象较多,全部复制的话效率较低,老年代更适合标记整理)
缺点:内存占用较高
面试题:说说GC和分代回收算法
黑色:沿着根对象已经找到了你这个对象并且这个对象的引用也在处理了。
灰色:沿着根对象已经找到了你这个对象,但是这个对象的引用还没有处理完成。
新生代:标记复制,老年代:标记整理
老年代垃圾回收器。并发失败(产生对象的速度>垃圾回收的速度),会进行full gc
随着这些eden区放满,会触发新生代的垃圾回收。为啥不多创建几个eden?eden大小会受新生代大小控制的。而G1新生代的内存占比是在5%~6%之间波动,所以eden就不能随便创建新的了。
当老年代的内存超过一定阈值就会触发并发标记,默认是老年代内存达到了堆内存的45%以上。
混合收集会根据暂停时间,优先对回收价值高的区域(存活对象少的区域)进行回收
解决方法:不要用自带的线程池,用自己定义的线程池,设置一个有
大小限制的任务队列。
救急线程没有上限,线程数耗尽了系统的线程资源
(调用类的静态成员变量也会触发类的初始化)
这个方法在类的初始化的时候被调用
看TestFinal的字节码
对于final修饰的基本数据类型,用到的类就会把这个值复制到自己的类中(即把这些值复制了一份放到了TestFinal类中)
(如果数字比较小直接放在方法区,如果数字比较大直接放到常量池中)
研究第二阶段---解析
第一个read时三个类都还没被加载
第二个read时
jdk8中类加载器
比如要加载String.class这个类,先问Application ClassLoader有没有这个类,发现没有,然后问Extension ClassLoader,也没有,最后问Bootstrap ClassLoader,把String.class加载到内存中。上级加载的类,所有的下级也可见。
再比如加载自己写的Student.class类,虽然Application ClassLoader中有这个类,但是也得先问上级Extension ClassLoader----->Bootstrap ClassLoader,上级都没有自己才可以加载这个类,并且加载的这个类上级不可见。
如果改成"b",则不会被垃圾回收,因为这样的话是在字符串常量池中的引用,这个引用是强引用。
ThreadLocalMap中Entry的key是弱引用,而key不是。如果垃圾回收会把key回收,value不会回收。如果使用不当会造成内存泄漏。
jdk中没有使用这种方法,因为这样成本有点高。
为什么最后要System.in.read()?因为后台这个Cleaner-0这个线程是一个守护线程,如果主线程停止了,守护线程也会直接停止,可能来不及做清理操作。
第一,从表面上我们能看出来finalize方法调用次序并不能保证(与垃圾回收的顺序有关,因为是按照入队的顺序来调用)
第二,日志中的Finalizer表示输出日志的线程名称,从这我们看出这个叫做Finalizer的线程调用的finalize方法
第三,你不能注释掉System.out.read(),否则会发现(绝大概率)并不会有任何输出结果了,从这我们看出finalize中的代码并不能保证被执行。
第四,如果将finalize中的代码出现异常,会发现根本没有异常输出
第五,垃圾回收时不会立刻调用finalize方法,会先把对象加到referqueue,referqueue里有一个线程,起一个调一次。
先调父类构造
把传入的狗对象包装成一个Finalizer对象,这个对象和虚引用弱引用对象类似,通过一个引用队列,就可以跟踪狗对象的垃圾回收过程。
unfinalized是一个双向链表,里面是Finalizer对象,这些Finalizer对象又真正引用了狗对象。作用是所有实现了finalize方法的对象都会放到这个链表中。
第一次垃圾回收时实现了finalize方法的对象无法被回收,只有执行完finalize方法后,第二次垃圾回收后该对象才会被回收。
ps:finalize线程优先级并不低,普通线程优先级为5,它为8
(串行加锁一个个执行,非常慢)