JVM
jvm : java虚拟机,运行在OS上,不与硬件进行交互。
类加载器,jMM,执行引擎,本地方法区。
首先,我们编译的.java文件通过编译器编译为.class文件,然后经过类加载器的加载,加载到运行时数据区,也就是JVM内存中,
不同的类加载器加载的class文件不同。
bootstrap class loader | 引导类加载器 | 使用原生c++代码实现,不继承java.lang.ClassLoader,加载核心类库 |
---|---|---|
extensions class loader | 扩展类加载器 | 加载扩展的Java类库,比如javax包 |
appclassloader | 应用类加载器 | 加载当前应用的classpath下指定的类 |
java.lang.classloader子类 | 自定义类加载器 | 用户自定义的加载方式 |
双亲委派机制:为了保证安全,在类加载器加载时,都会先用引导类加载器进行加载,无法加载再用扩展类加载器,最后才轮到自定义类加载器。
当任何一个加载器收到了类加载的请求,首先会让父类尝试加载,然后是母类,最后无法完成加载请求才会轮到应用类加载器。
每一个.class文件的字节码数组中都有一个文件表示,为了防止类加载加载到篡改文件
当class文件加载进JVM中,就变成了独立的Class对象,jvm通过包名和使用的类加载器来区分是不是同一个类。
类加载器只负责加载class文件,至于能不能执行是执行引擎的工作。
native
只要方法标注了native,就会到本地方法栈中运行,调用本地方法库中的c语言函数
PC寄存器
是一块几乎可以忽略不计的内存空间,不存在GC,里面保存的是即将要执行的下一个方法的地址。
当遇到native方法时,计数器为空,并且不会发生OOM。
方法区
方法区中保存的是供所有线程都可以访问的资源,最重要的就是类的结构信息,比如运行时常量池,构造方法和普通方法的字节码内容。
在jdk7和8有区别,在7以前这块区域叫永久代,在8叫元空间。
stack栈
栈管运行,堆管存储。
栈空间是线程独立的,所有的局部变量都存在栈里,并且生命周期跟随线程,不存在GC。
栈中保存的数据:
实例方法,8种基本数据类型,对象的引用
栈空间的默认大小是256K-1MB
如果超出的话会报出StackOverFlowError
栈中保存对象的引用,指向堆中的实例,堆中保存了访问元数据的地址。
堆
堆内存占JVM的95%以上的空间,是Jvm的主要工作内存,堆在逻辑上分为三块区域,分别为新生代、老年代、和元空间,在jdk8以前是永久代,Jdk8以后移除了永久代改为元空间,其中新生代又分为伊甸区,幸存0区,幸存2区。
每一个new的对象是在伊甸区产生并保存的,当伊甸区达到容量上限则会触发GC,利用垃圾回收算法将可回收的垃圾对象销毁,然后将没有被GC清除的对象移入幸存0区,当幸存0区满了又会GC移入幸存1区,幸存1区满了移入老年代,老年代满了触发Full GC,如果执行了Full GC还是无法进行对象保存,那么会抛出OutOfMemoryError错误。
GC 过程 就是复制清空交换。
所有的对象都是从伊甸区创建,当伊甸区满了会触发轻GC,然后复制不能被回收的对象到幸存0区,将伊甸区的对象全部清除,再执行轻度GC的时候,会将幸存0区和伊甸区的对象再进行一轮GC,将不能回收的放进幸存1区,然后幸存1区和0区进行交换,没有对象的区域为幸存1区。经历一次GC的对象 年龄被会+1,当年龄达到15时如果还存活就会放进老年代。
永久代即元空间,是一个常驻内存区域,几乎不会发生GC,目的就是为了实时可用,存放的是JDK自身携带的class和interface,是运行环境所必须的类信息。
JVM参数调整
-Xms | 设置Jvm初始值大小,默认为本机内存的1/64 |
---|---|
-Xmx | 设置Jvm最大的内存,默认为本机内存的1/4 |
-XX+PrintGCDetails | 输出详细的GC处理日志 |
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
元空间不使用堆内存,直接使用系统物理内存。
GC日志
gc回收算法
新生代发生的GC叫轻GC。 老年代发生的GC叫full GC,并且也会对新生代执行一次轻Gc ,但是不一定,而且FULL GC一般比轻GC要慢8-10倍,因为老年代占堆内存的2/3。
判断对象是否死亡: 引用计数算法,可达性分析算法
四个垃圾回收算法:标记清除算法、复制算法、标记整理算法、分代收集算法
七个垃圾回收器:Serial,SerialOld,ParNew,Parallel Scavenge,Parallel Old,CMS,G1.
引用计数法
通过判断对象是否被其他对象所引用,用一个计数器来维护,如果对象等于0那么就说明可以被回收。
缺点:需要维护计数器,且计数器也需要消耗,而且较难解决循环引用。
可以使用system.gc()手动GC,但是不一定执行。
复制算法
新生代采用复制算法,即每一次gc都会将清空伊甸区,并将存活的对象复制到from中,下一次再从from复制到to中,然后from转为to,保证to中没有数据,所以from和to的大小是一样的,而且不会有标记和清除的过程不会产生内存碎片,缺点就是需要双倍的空间。
对象每在幸存区存活一次,年龄就会+1,到达15将存入老年代。
这个值可以使用参数指定,最大15,因为对象的二进制头中使用四个字节存放年龄信息,最大为1111即15。
标记清除算法
发生在老年代,解决了空间的问题,即先使用判断对象是否可回收的算法,然后标记,再清除。
缺点,需要遍历两次,相比复制更费时,而且由于标记的对象是随机分配的内存地址,会产生内存碎片,清理出来的内存是不连续的,JVM不得不维护一个内存的空闲列表,而且在创建数组对象的时候,会不太好找。
标记清除整理
步骤和标记清除一样,也是发生在老年代,在标记清除后进行内存连续整理,但是缺点就是比较耗时。
是目前最好的算法。
总结
没有最好的算法,只有最合适的算法。
JMM
由于JVM运行程序的实体是线程,每个线程在创建时jvm都会为它创建一个工作内存,java内存模型中规定所有的变量都存储在主内存,是共享的所有的线程都可以访问但是不能直接修改,必须在自己的工作内存中读取赋值,首先要将变量从主内存中拷贝一份到工作空间,操作完成后再写回主内存,不能直接操作主内存的变量,也无法访问别的线程中的工作内存,线程之间的通信必须通过主内存来完成。
JMM是一种概念,并不存在,Jmm规范:可见性,原子性,有序性。