欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

java虚拟机内存,java虚拟机

来源: javaer 分享于  点击 24816 次 点评:91

java虚拟机内存,java虚拟机


一、JDK简介
1、

  • JDK(java development Kit) 包括 java程序设计语言、java虚拟机、Java API类库 三部分,
    是支持java程序开发的最小环境。

  • JRE(java Runtime Enviromet)包括 Java API类库中的Java SE API子集和java虚拟机
    两部分,是支持java程序运行的标准环境。

2、Sun HotSpot VM是目前使用最广泛的Java虚拟机。
3、java技术的未来:

  • 模块化

  • 混合语言,举例:Clojure、JRuby、Groovy、Scala等都可在JVM上运行

  • 多核并行

  • 进一步丰富语法

  • 64位虚拟机

二、Java内存区域与内存溢出异常

1、程序计数器:线程私有,一块较小内存空间,当前线程执行字节码的行号指示器,线程运行、切换、恢复时记录执行的字节码指令地址位置。
2、java虚拟机栈:线程私有,生命周期和线程相同。描述的是java方法执行的内存模型

  • 每个方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。从调用到执行完成的过程对应一个栈帧在虚拟机栈中入栈到出栈的过程。

  • 局部变量表存放了编译器可知的8种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址),所需内存大小编译期确定。最小存储单位是slot,第一个存放的是this对象引用,其后是方法参数变量、方法局部变量。

  • 该区域规定了2种异常: StackOverflowError 请求栈深度大于虚拟机允许的深度 OutOfMemeryError
    虚拟机栈动态扩展时无法申请到足够内存。

3、本地方法栈:和虚拟机栈类似,为Native方法服务,HotSpot虚拟机已将该栈和虚拟机栈合并。
4、java堆

  • 线程共享,虚拟机启动时创建,唯一目的就是存放对象实例,存放几乎所有对象实例以及数组,GC堆。

  • 线程私有缓冲区内存从Java堆中划分

  • 物理上是不连续的内存空间,逻辑上连续

5、方法区

  • 线程共享,存储已被虚拟机加载的类信息(版本、字段、方法、接口)、常量、静态变量、即时编译器编译后的代码等数据(多个类实例共享数据,如静态变量、类hashCode、指向锁记录的指针,线程ID、对象分代年龄)

  • HotSpot将方法区作为Java堆的一部分,当做永久代(Permanent Generation)

  • HotSpot JDK1.7已将永久代的字符串常量池(hashMap表)移到堆内存中。

6、运行时常量池

  • 方法区的一部分,存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中。

  • 字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;

  • 符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和

描述符。
7、直接内存:Native函数库直接分配堆外内存
8、HotSpot对象的创建
(1)虚拟机遇到new指令时,首先检查该指令参数在常量池中能否定位到一个类符号的引用。并且检查这个符号代表的类是否已被加载、解析和初始化过
对象所需要的内存在类加载检查后大小已完全确定。
a、内存规整使用“指针碰撞”分配内存,即将指针像后方空闲内存移动分配内存大小的偏移量,Serial、ParNew等带有整理内存功能的收集器使用该方法分配。
b、堆内存不规整使用“空闲列表”分配内存,维护一个列表记录哪些内存是可用的,分配内存时在列表中找到一个内存足够大的空间划分给对象实例
(2)多线程情况则非线程安全
a、使用cas+失败重试的方式保证更新操作的原子性
b、按照线程划分在不同的空间之中进行,分配内存时先在本地线程分配缓冲(TLAB),只有TLAB用完并分配新的TLAB时,才需要同步锁定。

(3)对象内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),接下来要对对象进行必要的设置,如对象是哪个类的实例,如何才能找到类的元数据信息、对象的hash码、GC分代年龄等信息。

8、对象的内存布局:对象头、实例数据、对齐填充 3大块

  • 对象头(存储在方法区):一部分存储对象自身运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分是类指针,指向类元数据的指针,确定这个对象是哪个类的实例。java数组对象还必须有一块记录数组长度的数据。

  • 实例数据(存储在堆内存):对象真正存储的有效信息,包括父类继承字段,相同宽度的字段总是被分配到一起,然后是父类定义字段出现在子类前面。

9、对象的访问定位(对象类型数据存储在方法区)

  • 句柄访问:在Java堆中划分出一块内存作为句柄池,reference存储句柄地址,句柄包含了对象实例数据和类型数据地址信息。优点是移动回收对象时只需改变句柄中的信息。

  • 直接访问(HotSpot默认):reference直接存储对象地址,对象包含指向对象类型数据的指针,优点是访问速度快。

10、-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
堆内存20M,不可扩展,出现内存溢出时Dump出当前的内存堆转储快照

报错:java.lang.OutOfMemoryError: Java heap space
  • 解决方案:通过Eclipse Memory Analyzer打开堆转储快照文件

  • 内存泄漏:通过对象到GC Roots的引用链,定位泄漏代码位置。

  • 非内存泄漏:检查虚拟机的堆参数(-Xmx与-Xms),与物理机对比看是否能调大。检查代码是否存在某些对象生命周期过长、持有状态时间过长的情况。

11、虚拟机栈和本地方法栈溢出
-Xss 设定栈容量
12、方法区和运行时常量池溢出(虚拟机永久代)
JDK1.6版本及之前版本通过 -XX:PermSize和-XX:MaxPermSize限制方法区大小,运行时常量池溢出,java.lang.OutOfMemoryError: PermGen space
13、直接内存溢出

14、java.lang.OutOfMemoryError: Java heap space 堆内存溢出
java.lang.OutOfMemoryError: PermGen space 方法区内存溢出,运行时常量池溢出(HotSpot永久代)
java.lang.StackOverflowError 线程请求的栈深度大于虚拟机所允许的最大深度
java.lang.OutOfMemoryError:unable to create new native thread 创建线程数量过多,无法分配线程所需内存

三、垃圾收集器与内存分配策略
1、程序计数器、虚拟机栈、本地方法栈随线程而生、灭,线程结束时,内存自然就释放了。
Java堆内存和方法区是动态的,GC主要针对该内存回收。
2、可达性分析算法
通过一系列的称为“GC Roots”的对象作为起点,从这些节点向下搜索,搜索走过的路径称为引用链,一个对象没有任何引用链相连表明该对象不可用,回收掉。
可作为GC Roots对象的对象包括:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

强引用:不会被GC回收
软引用:还有用但非必须的对象,在系统将发生内存溢出异常之前,会把这些对象列进回收范围之中进行第二次回收。
弱引用:非必须的对象,弱引用对象只能生存到下一次垃圾收集发生之前。
虚引用:和无引用差不多
3、回收方法区
永久代的垃圾收集主要回收两部分:废弃常量和无用的类。
无用的类必须满足一下3个条件
(1)该类的所有实例都已被回收
(2)加载该类的ClassLoader已经被回收
(3)该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过发射访问该类。
4、垃圾收集算法

  • 标记-清除算法:标记和清除的效率都不高、 产生大量不连续的内存碎片。

  • 复制算法:新生代中98%的对象朝生夕死,HotSpot按8:1的比例分成一个Eden和2个Survivor空间,浪费10%内存空间,。每次使用Edon和一块Survivor空间,MInor
    GC后将存活对象复制到另一块Survivor空间。如果Survivor内存不够则需依赖老年代内存进行分配担保。缺点:对象存活率较高时,效率低。

  • 标记-整理算法:标记和第一种一样,后续步骤将存活对象向一端移动,然后直接清理调端边界以外的内存。

  • 分代收集算法:把Java堆分为新生代(复制算法)和老年代(标记-整理算法),不同代采用上面合适的方法。

5、枚举根节点
GC停顿:可达性分析工作必须在一个能确保一致性的快照中进行,导致GC进行时必须停顿所有java执行线程。

HotSpot回收
(1)使用一组称为OopMap的数据结构,在类加载完成时会把对象内具体偏移量位置是什么类型的数据计算出来,在JIT编译的过程中,也会在特定的位置(下面介绍)记录下栈和寄存器中哪些位置是引用。因此GC扫描时直接通过OopMap获取栈和寄存器中引用堆对象引用的具体位置,避免栈和寄存器全局扫描。
(2)上面“特定的位置”称为安全点(safePoint),GC时只有到达安全点时才能暂停,safePoint的选定基本上以程序“是否具有让程序长时间执行的特征”为标准选定。指令执行的时间都非常短暂,所以只有在 指令流长度太长时生产safePoint,如方法调用、循环跳转、异常跳转等。
(3)主动试中断:对每个线程设置一个标识,每次执行到safePoint或者创建对象需要分配内存时 检查该标识是否为真,为真则中断线程执行。
(4)安全区域:线程sleep或者Block状态,线程未获取到CPU执行,此时不能进入到safePoint,这种代码片段称为安全区,GC可以安全回收,但唤醒线程之前必须先收到GC已完成的信号。

6、垃圾收集器(HotSpot虚拟机提供的收集器)

吞吐率最优: Parallel Scaverge + Parallel Old

(1)新生代:Serial、perNew、Parallel Scaverge、G1
老年代:CMS、serial Old、Parallel Old、G1
(2)Serial收集器:复制算法,单线程,进行垃圾收集时,必须暂停其他所有工作线程。对于运行在client模式下的虚拟机或单CPU是一个很好的选择。

(3)ParNew收集器:复制算法,Serial收集器的多线程版本,适合多核CPU,并行。

(4)Parallel Scaverge收集器:复制算法,目标是达到一个可控制的吞吐量,吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间),适合高并发后台运算而交互任务少。
(5)serial Old收集器:标记-整理算法
(6)Parallel Old收集器:标记-整理算法,Parallel Scaverge收集器的老年代版本。
(7)CMS(Concurrent Mark Sweep)并发收集器:标记-清除算法,以获取最短回收停顿时间为目标,适合互联网或者B/S系统多CPU服务。分为4个步骤:

  • 初始标记:速度快,需停掉所有用户线程。

  • 并发标记:与用户线程并发执行。

  • 重新标记:比初始标记时间稍长,需停掉所有用户线程,目的是标记并发标记阶段用户线程导致标记改变的对象。

  • 并发清除:与用户线程并发执行。

缺点:并发对CPU资源非常敏感,适合CPU大于等于4核,否则效率会很低。
无法处理浮动垃圾,并发时用户线程会产生很多新的垃圾,需要预留部分内存,等到下次GC回收。 也可能出现回收失败导致一次Full GC的产生,产生“Concurrent Mode Failure”失败,会启动使用serial Old收集器收集老年代作为后备预案。
采用标记-清除算法,有大量空间碎片,可通过参数配置多少次Full GC之后整理空间(默认开启)。

(8)G1收集器:JDK1.7版本才有,面向服务端。优点:
- 并行与并发:同CMS
- 分代收集:独立管理新生代和老年代空间区
- 整合空间:标记-整理算法
- 可预测的停顿:使用者可以明确指定消耗在垃圾收集器上的时间不超过N毫秒。
将Java堆划分为多个大小相等的独立区域(Region),新生代和老年代不再是物理隔离,他们都是一部分Region(不需要连续)的集合。回收以Region为单位,每次回收价值最大的Region,化整为零。

G1 region之间或者其他收集器新生代与老年代的对象引用,虚拟机都是使用Remenbered Set 来避免全堆扫描。G1中每个Region都有一个Remenbered Set,当A region中的对象引用了B Region中的对象,则在B Region的Remenbered Set记录被引用的对象reference.

  • 初始标记:仅仅标记GC Root能直接关联到的对象。标记并且修改TAMS的值,让下一阶段用户程序并发执行。需要停顿所有线程,但时间很短。
  • 并发标记:从GC Root开始对堆对象进行可达性分析,找出存活的对象,耗时较长,但可与用户程序并发执行。
  • 最终标记:并行执行,停掉所有用户线程,目的是标记并发标记阶段用户线程导致标记改变的对象,虚拟机将这段时间对象变化记录在Remenbered
    Set Log中,最终标记阶段需要把Remenbered Set Log的数据合并到Remenbered Set中。
  • 最后筛选:首先对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间指定回收计划

7、GC参数: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

  • 分配堆内存20M,不可扩展,其中10M分配给新生代,剩下10M分给老年代。

  • -XX:+PrintGCDetails打印GC日志,

  • -XX:SurvivorRatio=8决定新生代中Edon区与一个Survivor区的空间比例是8:1

  • 大部分对象优先分配到新生代Eden区

8、(1)Minor GC回收从新生代Eden进入Survivor区,对象每熬过一次Minor GC后年龄计数器加一,超过一定程度(默认15岁)被晋升到老年代,可以通过 -XX:MaxTenuringThreshold参数设置。
(2)如果在survivor空间中相同年龄所有对象大小综合大于Survicor空间一半,年龄大于或等于改年龄的对象会直接进入老年代。
(3)大对象可以直接在老年代分配,通过 -XX:PretenureSizeThreshold参数设置对象大小阈值。
(4)老年代最大可用的连续空间小于当次GC时新生代所有对象所有空间,如果大于每次晋升平均大小,将尝试一次Minor GC,如果小于每次晋升平均大小将进行一次Full GC。
9、空间分配担保
(1)步骤
检查判断老年代最大可用连续空间是否大于新生代所有对象总空间
查看HandlePromotionFailure设置值是否允许失败,允许则检查判断老年代最大可用连续空间是否大于历次晋升到老年代对象的平均值

  • 上面2个步骤满足任意一个进行Minor GC,都不满足进行Full GC

  • 在JDK6 Update24之后规则变为只要老年代最大可用连续空间是否大于新生代所有对象总空间或者历次晋升到老年代对象的平均值就进行Minor GC,否则进行Full GC。

10、除了java堆和永久代之外,下面这些区域也会占用较多的内存,这里所有的内存总和受到操作系统进程最大内存的限制

  • Direct Memery,可通过-XX:MaxDirectMemerySize调整大小,内存不足时抛出OutOfMemoryErrer或者OutOfMemoryErrer:direct buffer memory

  • 线程堆栈(请看上面的具体描述)

  • Socket缓存区:每个Socket都有Receive和Send两个缓存区,连接较多时内存占用比较可观,会抛出IOException:Too
    many open files异常

  • JNI代码:本地库使用的内存也不再堆中

  • 虚拟机和GC:虚拟机、GC的代码执行也要消耗一定的内存

四、java虚拟机性能监控与故障处理工具
bin目录下所有监控工具具体代码实现在tools.jar类库,tools.jar类库不属于java标准的API
1、jps(JVM Process Status Tool)虚拟机进程状况工具
2、jstat(Java Statistic Monitoring tool)用于监视虚拟机各种运行状态信息的命令行工具
3、jinfo(Configuration Info For Java)的作用是实时地查看和调整虚拟机各项参数。
4、jmap(Memery Map For Java)命令用于生成堆转存储快照(一般称为heapdump或者dump文件)
5、jhat(JVM Heap Analysis Tool)分析jmap生成的堆转储快照,一般不会被使用。
6、jstack(Stack Trace For Java)命令用于生成虚拟机当前时刻的线程快照,就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。目的是定位线程出现长时间停顿的原因,如线程间死锁、四循环、请求外部资源导致的长时间等待等。
java.lang.Thread类有一个getAllStackTraces()方法用于获取虚拟机中所有线程的StackTraceElement对象。
java.lang.Thread类有一个getAllStackTraces()方法用于获取虚拟机中所有线程的StackTraceElement
7、HIDIS:JIT生成代码反汇编
8、JConsole:java监视与管理控制台
9、visualvm:多合一故障处理工具

五、Eclipse调优
1、升级JDK
2、虚拟机内置了两个运行时编译器,如果一段java方法被调用次数达到一定程度,就会被判断为热代码交给JIT编译器及时编译为本地代码。随着代码被编译的越来越透彻,运行速度应当是越运行越快。
3、调整内存设置控制垃圾收集频率
设置-Xms和-XX:PermSize参数值设置为-Xmx和-XX:MaxPermSize一样,强制启动时把新生代和老年代的容量固定下来,避免运行时自动扩展和Full GC
-Xverify:none
-XX:+DisableExplicitGC屏蔽掉System.GC()
4、选择收集器降低延时
-XX:+UseConcMarkSweepGC、-XX:+UseParNewGC,新生代和老年代分别使用ParNew和CMS收集器进行垃圾回收

相关文章

    暂无相关文章
相关栏目:

用户点评