追本溯源,都知道c和python,c使用编译的方式实现,而python是使用翻译器使用,而java不同,即使用编译又使用翻译器,jvm是满足java跨平台的特性,利用jvm可以将字节码文件(就是编译后的.class)变成计算机可识别的机器码。
jvm包含类加载器,执行引擎,运行时数据区。
它是把class文件变成实例的一步,加载过程分为载入,验证,准备,解析、初始化。例如实现代码加密来避免核心代码泄漏、解决不同服务依赖同一个包的不同版本所引起的冲突问题以及实现程序热部署来避免调试时频繁重启应用。它遵从双亲委派原则,那什么双亲委派原则了?
目前类加载器分为:
启动类加载器
BootstrapClassLoader
管理lib
扩展类加载器
ExtClassLoader
lib/ext
应用程序加载器
AppClassLoader
用户类路径
我们也可以自定义类加载器
双亲委派原则,任意一个类加载器都会委托父类去加载,也就所有加载器都优化找顶级父类加载器,好处是避免了重复加载,不同加载器加载内存中出现混乱。
主要为翻译器和jul编译器,jul编译器将字节码流变成本地代码保存效率更高。
程序计数器,一块较小的内存空间,是当前线程的所执行的字节码指示器,就是指令地址。不会随程序的运行空间改变,也就不会oom
jvm和寄存器一样是线程私有的,每一个 JVM 线程都有自己的 JVM 栈,这个栈与线程同时创建,它的生命周期与线程相同。
Java 虚拟机栈中每个方法是一个个栈帧,每个栈帧对应一个被调用的方法。当线程执行一个方法时,会创建一个对应的栈帧,并将栈帧压入栈中。当方法执行完毕后,将栈帧从栈中移除。
某些方法中的对象引用没有被返回或者未被外面使用,可以在栈上分配内存。
JVM 可能会使用到传统的栈来支持 Native 方法(C语言)的执行,这个栈就是本地方法栈。
在 JVM 中,堆是可供各条线程共享的运行时内存区域,也是供所有类实例和数据对象分配内存的区域。此内存区域的唯⼀⽬的就是存放对象实例, Java ⾥“⼏乎”所有的对象实例都在这⾥分配内存。在jdk1.8后字堆只包含字符串常量池。
当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生该错误。
假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发该错误。和本机的物理内存无关,和我们配置的虚拟机内存大小有关!
大部分我们创建的对象,都属于生命周期比较短的,所以会存放在新生代。新生代又细分 Eden 空间、From Survivor 空间、To Survivor 空间,我们创建的对象优先在 Eden 分配。
当eden满了,发生minor gc ,清空未引用对象,存活对象进入from
survivor ,当后面在eden满了,再次minor gc ,eden和from survior中的幸存对象进入to survior,之后就是from 和to空间循环,这也解释为什么使用两个幸存空间,如果只有一个空间,会有空间碎片。
当年龄到达16岁时候,触发幸存者进入老年代,有标准参数-XX:MaxTenuringThreshold默认值是15可以设置。
大对象直接进入老年代,参数值是XX:PretenureSizeThreshold 来设置这些大对象的阈值。
动态年龄判断,除了年龄达到 MaxTenuringThreshold 的值,还有另外一个方式进入老年代,那就是动态年龄判断:在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
新生代用复制
当年老年代空间不够用的时候,虚拟机会使用“标记—清除”或者“标记—整理”算法清理出连续的内存空间,分配对象使用。
Full GC: 收集整个堆,包括 新生代,老年代,永久代(在 JDK 1.8及以后,永久代被移除,换为metaspace 元空间)等所有部分的模式
Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果大于,则发起 Minor GC。
如果小于,则看 HandlePromotionFailure 有没有设置,如果没有设置,就发起 full gc。
如果设置了 HandlePromotionFailure,则看老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果小于,就发起 full gc。
如果大于,发起 Minor GC。Minor GC 后,看 Survivor 空间是否足够存放存活对象,如果不够,就放入老年代,如果够放,就直接存放 Survivor 空间。如果老年代都不够放存活对象,担保失败(Handle Promotion Failure),发起 full gc。
在 JVM 中,被加载类型的信息都保存在方法区中。包括类型信息(Type Information)和方法列表(Method Tables)。方法区是所有线程共享的,所以访问方法区信息的方法必须是线程安全的。
运行时常量池是每一个类或接口的常量池在运行时的表现形式,它包括了编译器可知的数值字面量,以及运行期解析后才能获得的方法或字段的引用。简而言之,当一个方法或者变量被引用时,JVM 通过运行时常量区来查找方法或者变量在内存里的实际地址。
顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
移动起来比较麻烦,效率没有复制算法高,适合于老年代,存活对象比较多情况,移动的情况比较少。
把有效对象放在一边,其他一边直接清理,适用于新生代,移动较少对象,存活比较少的情况
因为内存都是连续使用的空间,内存碎片是个问题
强引用:最传统的引用,如 Object obj = new Object() 。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用:在内存溢出之前,进行垃圾回收,如果还是内存不足的话,就会oom
弱:只要发生垃圾回收,就会被回收
虚:只是垃圾回收发个通知