- 程序设计语言,Java虚拟机,Java类库统称为JDK。
- Java Api类库中的JavaSE Api子集和java虚拟机这两部分统称为jre。
- Java虚拟机运行时数据区
- 程序计数器
- 线程私有
- 当前线程所执行的字节码的行号指示器。
- Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的。每条线程都需要有一个独立的程序计数器。
- 执行Java方法存放字节码
- Java虚拟机栈
- 线程私有,生命周期与线程相同。
- 描述Java方法执行的内存模型,每一个方法被调用直至执行完成的过程,就对应着一个栈帧从入栈到出栈的过程。
- 局部变量表所需的内存空间在编译器间完成分配。
- StackOverflowError和OutOfMemoryError异常。
- 本地方法栈
- 为Native方法服务
- StackOverflowError和OutOfMemoryError异常。
- Java堆
- 所有线程共享,虚拟机启动时创建,存放对象实例。也被称为GC堆。
- 物理上不连续,逻辑连续。
- OutOfMemoryError
- 方法区
- 线程共享,存放已被加载的类、常量、静态变量、即时编译器编译后的代码等数据
- 不需要连续的内存,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
- 运行时常量池
- 存放编译器生成的各种字面量和符号引用,也有翻译出来的直接引用
- OutOfMemoryError
- 直接内存
- 并不是虚拟机运行时数据区的一部分
- 基于Channel和Buffer的I/O方式,可以使用Native函数库直接分配堆外内存,通过Java堆里DirectByteBuffer对象引用,受本机总内存及处理器寻址空间限制。
- OutOfMemoryError
- 对象访问
- reference类型访问方式:使用句柄和直接指针
- 句柄:
- Java堆中划分一块内存作为句柄池,存放对象的句柄地址
- 好处是reference中存储的是稳定的句柄地址,在对象被移动时只改变句柄中的实例数据指针,reference不需要被修改。
- 直接指针
- Java堆中放置访问类型数据的相关信息,reference中直接存储的就是对象地址
- 好处是速度更快
- 句柄:
- reference类型访问方式:使用句柄和直接指针
- 程序计数器
- OutOfMemoryError
- Java堆溢出
- 提示java.lang.OutOfMemoryError,接着提示Java heap space
- 解决方法
- 通过内存映像分析工具确认内存中的对象是否是必要的,如果是内存泄露,可查看泄露对象到GC Roots的引用链。如果不是可查看虚拟机的堆参数。
- 虚拟机栈和本地方法栈溢出
- 建立过多线程导致的内存溢出,只能通过减少最大堆和减少栈容量来换取更多的线程
- 运行时常量池溢出
- String.intern()这个Native方法往常量池中添加内容
- 方法区溢出
- 本机直接内存溢出
- Java堆溢出
- 垃圾收集器与内存分配策略
- 程序计数器、虚拟机栈、本地方法栈三个区域属于线程私有,方法结束或线程结束时,内存就跟随着回收了。
- Java堆
- 两次标记过程
- 第一次:没有与GC Roots相连接的引用链,标记并筛选
- 第二次:筛选中被判定有必要执行finalize方法,这个对象会被放置在F-Queue队列中,会由一条虚拟机自动建立的低优先级的Finalizer线程执行,然后进行第二次标记
- 任何一个对象的finalize()方法只会被系统调用一次,如果对象面临下一次回收,他的finalize()方法不会被再次执行
- 计数算法:
- 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。但Java语言中没有选用引用计数算法来管理内存,因为它很难解决对象之间的相互循环引用的问题。
- 根搜索算法(Java、C#、Lisp):
- 在Java语言中,可作为GC Roots的对象包括
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(Native方法)的引用对象
- 通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的。
- 引用
- 虚引用
- 唯一目的就是希望能在在这个对象被收集器回收时收到一个系统通知
- 强引用:
- 类似“Object obj = new Object()”,不会被回收
- 软引用
- 非必需,系统将要发生内存溢出异常之前,将会把这些对象列进回收范围并进行第二次回收,如果还是没有足够内存,才会抛出异常
- 弱引用
- 只能生存到下一次垃圾收集发生之前
- 虚引用
- 在Java语言中,可作为GC Roots的对象包括
- 两次标记过程
- 方法区
- 主要回收废弃常量和无用的类
- 常量池的回收类似Java堆
- 判断“无用的类”
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
- 该类所有的实例都已经被回收
- 加载该类的ClassLoader已经被回收
- 垃圾收集算法
- 标记-清除算法
- 首先标记出所有需要回收的对象,在标记完成后统一回收
- 缺点:效率不高,会产生内存碎片,碎片太多可能会导致无法分配较大的连续内存
- 复制算法
- 缺点:内存缩小
- 将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将存活着的对象复制到另一块上,然后把已使用过的内存空间一次清理掉。现在发展为将内存分为一块较大的Eden空间和两块较小的Survivor空间。
- 标记-整理算法(老年代)
- 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
- 分代收集算法
- 根据对象的存活周期的不同将内存划分为几块,根据各个年代的特点采用最适当的收集算法
- 应用于商业虚拟机
- 标记-清除算法
- 内存分配
- 大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间时,虚拟机将发起一次Minor GC
- 大对象(需要大量连续内存空间的Java对象,大于-XX:PretenureSizeThreshold参数)直接进入老年代,避免在Eden区及两个Survivor区之间发生大量的内存拷贝。(PretenureSizeThreshold只对Serial和ParNew两款收集器有效)
- 长期存活的对象将进入老年代,虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能移动到Survivor空间中,则Age+1,当Age>-XX:MaxTenuringThreshold时晋升老年代
- 动态对象年龄判定
- 空间分配担保
- 在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,大于则进行Full GC,如果小于,则查看HandlePromotionFailure设置是否允许担保失败,允许就Minor GC,不允许就Full GC
- 虚拟机性能监控与故障处理工具
- 采用Java代码来实现这些监控工具
- 借助tools.jar类库里面的接口,tools.jar中的类库不属于Java的标准API
- jps:虚拟机进程状况工具
- jstat:虚拟机统计信息监视工具
- jinfo:Java配置信息工具
- jmap:Java内存印象工具
- jhat:虚拟机堆转储快照分析工具
- jstack:Java堆栈跟踪工具
- JConsole:Java监视与管理控制台
- 内存监控
- 线程监控
- VisualVM:多合一故障处理工具
- 生成和浏览堆转储快照
- 分析程序性能
《深入理解Java虚拟机》随笔
猜你喜欢
转载自blog.csdn.net/Evan_CaoM/article/details/84292418
今日推荐
周排行