JVM
优点
- 跨平台
- 自动内存管理,垃圾回收功能。
- 数组下标越界自动检查抛异常(其他语言需要程序员自己去处理,否则会新元素覆盖当前元素,后果很严重)
- 多态:使代码的可扩展性巨大提升
比较
- jvm:频闭操作系统,对外开放一个通用的环境
- jre:java 运行时环境
- jdk:java工具包(包括编译工具javac等)
学习jvm有什么用
- 面试
- 理解底层实现原理
- 中高级程序员的必备技能
JVM内存结构
程序计数器:Program Counter Register(寄存器)
解释器拿到jvm指令解释成机器码后交给cpu处理。
- 作用:是记住下一条jvm指令的执行地址;
- 特点
1. 是线程私有的(每个线程都有自己的程序计数器);
2. 不会存在内存溢出。
虚拟机栈,本地方法栈
- 栈是线程私有的,每个线程都有一个独立的栈,栈里边存放的是栈帧,栈帧里边存放的是方法的参数、变量、方法的返回地址。
- 当线程执行方法时,每个方法都会产生一个栈帧,栈帧根据方法的开始执行和返回地址结束执行,进行入栈和出栈动作。
- 虚拟机栈和本地方法栈作用一致,区别是虚拟机栈是Java调用Java代码,本地方法栈是Java调用其他语言(比如Java底层调用计算机硬件)
堆
- 堆是Java虚拟机所管理的内存中最大的一块,Java堆是被所有线程所共享的,在虚拟机启动时创建,此内存区域唯一的目的就是存放对象实例。
- Java堆是垃圾收集器管理的主要区域。
- 堆可细分为:新生代和老年代。
方法区(堆的逻辑部分)
- 也被称为“永久代”,与堆一样,是各个线程共享的内存区域。
- 它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- jdk1.8已经没有永久代,取而代之的是另一块与堆内存不相连的本地内存(元空间)
运行时常量池
- 运行时常量池是方法区的一部分。Class文件中除了有类版本、字段、方法、接口等描述信息外,还有一项信息是常量池、用于存放编译期生成的各种字面量的符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
- 常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
垃圾回收
何为死亡
-
引用计数法(java虚拟机未使用)
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
引用计数算法的实现简单,判定率也很高,也有一些比较著名的应用案例比如Python。
Java虚拟机没有选用引用计数法来管理内存,最主要的原因是它很难解决对象之间相互循环引用的问题。 -
可达性分析算法
在主流的商用程序语言(Java、C#)都是通过可达性算法来判定对象是否存活。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(从GC Roots到这个对象不可达)时则证明此对象是不可用的。
“GC Roots”对象可理解为,本次垃圾收集器回收时一定不会被回收的对象。(比如当前加锁的对象,当前线程正在引用的对象/变量,java核心对象)。
引用
在JDK1.2 以后java对引用概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用,这四种引用强度依次逐渐减弱。
- 强引用
只要强引用还在垃圾收集器永远不会回收掉被引用的对象。 - 软引用
在系统将要发生内存溢出异常之前,将会把这些对象回收,如果回收后还是没有足够的内存,才会抛出异常。 - 弱引用
被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。 - 虚引用
一个对象是否有虚引用的存在,完全不会对其生存的时间构成影响,也无法通过虚引用来取得一个对象实例。
为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
回收方法区
方法区(永久代,jak1.8对方法区有改进,上边有提到)回收性价比一般比较低,在堆中,尤其是新生代,常规应用进行一次垃圾收集一般可以回收70%~95%,而永久代的垃圾收集效率远低于此。
永久代垃圾收集主要回收两部分内容:废弃常量和无用的类。
垃圾回收机制算法
- 标记-清除
先标记再统一回收,两个阶段。效率低,空间碎片多,假如要给一个较大的对象分配空间,无法找到能存下该对象的连续的空间而不得不提前触发另一次垃圾收集动作。 - 复制算法
将内存分为大小相等的两块,每次只使用一块,当这块内存使用完后,就将活着的对象复制到另一块,然后将这块内存清理掉。
优点:实现简单,效率高,无内存碎片。
缺点:使用内存缩小了一半。
在业界利用复制算法,内存应该不是对半分(为了提高内存利用率) - 标记-整理
先标记,再移动,后清理,标记完后,将活着的对象向一端移动,清理掉端边界以外的内存。 - 分代收集算法
新生代使用复制算法:对象存活率低,复制的对象少,效率高。
老年代使用标记-清除或标记-整理:存活率高,无额外空间分配,所以使用标记清除或标记整理算法进行回收。