主要是记录学习jvm时的一些学习的博客以及个人理解(方便复习),有误的话,请大佬多多指教。待更新中。。。。。。
一,jvm博客园笔记
jvm运行时结构图(24道面试题)
java内存管理(10道面试题)
1, 双亲委托加载(类加载器):
确定一个类,依靠类的全限定类名+类的加载器
使用的是父系委托机制,使用的加载器如下分类:不同的类加载器指定的类加载路径不同。
1,引导类加载器是jdk核心类的加载路径:如String类和Object类的加载路径。
(%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes路径下的内容)
2,扩展类加载器:以Extension来分析:maven项目添加的包,lib包等包中的类的加载路径。
(jre/lib/ext子目录(扩展目录))
3,系统类加载器:”classpath:“路径,以及平时自己创建的类的加载路径。
(加载环境变量classpath或系统属性 java.class.path指定路径下的类库)
父系委托机制:如自创建一个String类,由3委托2,2委托1。当1中有时按照1中的类创建,1没有则由2判断是否拥有,最后才是3。顺序是3->2->1。
.注意上图中的加载器划分关系为包含关系,并不是继承关系。(父系不是父类!)
2,类的加载
jvm通过类加载器加载class文件,在方法区创建类的字节码内存块,将所有的类信息生成一个在堆的class对象
- 字节码内存块存放类的基本信息+常量池+加载的该类的类加载器对象的引用+生成的class对象的引用。
- class对象存放字节码内存块的所有信息包含类加载器的引用,所以反射的this.getClass().getDeclaredMethods(),this.getClass().getDeclaredMethods()都要先获取class对象。
- 实例对象在内存中存储的结构由三部分组成:对象头、实例数据、对齐填充。对象头里包含的class对象的引用,所以可以用this.getClass()获取class对象。
- 所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当使用到类的静态变量,或者类的对象时才会进行第一次加载。
- 所有的实例对象都是根据class对象的数据进行创建。
- new A()时,进行的操作有新建一个对象+对象初始化
二,
1,对象创建;标量替换(ps:快乐套娃);知乎的逃逸分析(建议直接看他的例子)
总结:个人理解,有误望大佬指出。
1,jvm默认开启逃逸分析,来分析对象是否逃逸。
2,非逃逸对象会被对象分解成若干个有被利用的成员变量放到虚拟机栈的栈帧的局部变量表或寄存器。(这里使用标量替换)
3,逃逸对象,小的对象会在堆的新生代的eden区的TLAN区分配内存。(TLAN区为每个线程在堆的eden区里私有的一小片区域)
大的对象会直接分配到堆的老年代区。(判断对象大小,看对象能不能放到TLAN区就行了。)
2,CG垃圾回收,可达性分析
1,存入老年代区的可能有三种:年轻代没地方放了,新创建的对象太大了,旧对象年龄太老了。(官网可知,不是超过15才算老,一些垃圾收集器cms是6,具体看链接)
2,判断对象是否无用有两种算法:
a,引用计数法(就是计算有没有引用该对象,没有就是无用对象,因为该算法有缺陷基本都没虚拟机用这种)。
b,可达性分析算法:主流的java虚拟机都用这种。
思路:判断对象的引用源头是不是GC Roots类型的对象。
GC Roots:四种,虚拟机栈,本地方法栈,方法区的常量(static final)和静态变量(final)。这四种有引用的对象。
c,即使用前两种算法得出无用对象,还可以使用 “两次标记” 来执行无用对象里的重写的finalize()方法来试图绑定GC Roots,使无用对象成有引用对象。(这是最后的一次机会了,之后的清理无用对象,年轻代区域用复制删除,老年代区域用标记删除,细节看链接)
d.总体采用分代收集算法:新生代:用复制算法; 老年代: 用标记整理算法进行回收
GC垃圾回收:垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。
- 对象创建时存放在堆的eden(伊甸)区。(其实是存放在每个线程在eden区私有的TLAN区,TLAN区满不会触发MGC)
- 当eden区无法存放新创建的对象时,触发一次年轻代的垃圾回收(Minor GC)。
- 年轻代垃圾回收:处理对象是eden区和form service区,垃圾回收算法是复制算法,结果是两个区有效对象到to service区,两个区清空。
- 复制算法:将内存分为两块,每次使用其中一块,另一块保留为空。当清理时,把有效对象放到空的内存块,另一个内存块直接清空。
- form service区与to service区每次GC都会出现其中一个区为空。当一个对象GC多次还保留在service中时存放到老年区(tentired)。
- 当老年区(tentired)无法存放新的对象时,触发一次所有区的垃圾回收(Full GC)
- 老年代垃圾回收:处理对象是所有区,垃圾回收算法是复制算法,清除标记算法,结果是年轻代使用Minor GC,老年代使用清除标记算法清除了无效对象。
- 清除标记算法:将内存中无用对象标记后清除,将有效对象整理到内存的一边。
- Full GC一次需要处理很长时间,会出现一次以上的Minor GC。FullGC的出现概率较少。
- 分配担保机制:在一个MGC中由于年轻代区域的eden与from service区都满载,而老年区有充足的内存空间可以存放新创建的对象,可以将这个对象存放到老年区。
3,方法区
1,方法区需要保存:常量池,类的所有信息(类方法,相关接口,相关继承类等等),所有方法的信息,域信息(像public等修饰符),jit编译热点代码后的代码缓存(二进制)等等。
2,Stringtable是放在堆里的(jdk1.8之后),不过String本来就是对象放在堆里才对,“kkk”这个常量才放在常量池里。(class文件的常量池最后也会放入运行时常量池中)。
3,类变量及静态成员变量应该是在方法区存放类的区域。
4,运行时常量池在方法区里。
4,符号引用
直接引用:直接指向实际的内存地址。
符号引用:本质上就是一段字符串,
编译一个类的时候不知道这个类里引用的其他类在内存哪里,所以就直接使用其他类的全限定类名作为暂时代替,
在虚拟机执行的时候会根据这个全限定类名去内存里寻找全限定类名指向的类的实际内存地址。
org.simple.People类引用org.simple.Tool类,在编译时People类并不知道Tool类的实际内存地址,因此只能使用符号org.simple.Tool(假设)来表示Tool类的地址。而在类装载器装载People类时,此时可以通过虚拟机获取Tool类 的实际内存地址,因此便可以既将符号org.simple.Tool替换为Tool类的实际内存地址,及直接引用地址。
XN(额外补充);JIT;对象的具体创建(略读中的略读);SPI;JNI(具体调用本地接口的过程,java用c);ThreadLocal(还是算可以吧)
GC收集器的使用
1,关于JIT,个人理解是对一些热点代码(多次使用的代码)会被jvm中的JIT编译器直接编译成机器码(二进制),以此提高效率,其他代码用jvm的解释器去解释执行。