深度解析java虚拟机

 

一、运行时数据区域

1.程序计数器

是一块较小的内存,负责在字节解码器工作的时候通过改变计数器的值获取下一条需要执行的字节码的指令,如分支,循环,跳转,异常处理等基础功能。在多线程中每个线程都有自己独立的程序计数器,它是线程私有的内存。程序计数器是JVM中唯一没有OutOfMemmoryError异常的内存。

2.Java虚拟机栈

Java虚拟机栈也是线程私有,他的生命周期和线程相同,每个方法执行的同事都会创建一个栈帧用于存放局部变量、操作数栈、动态链接、方法出口等信息。Java虚拟机栈也叫局部变量表,在程序编译期间完成内存分配,在运行期不改变局部变量表的大小。如果线程请求大于栈深将抛出StackOverflowError异常,如果扩展时请求不到足够的空间抛出OutOfMemmoryError异常。

3.本地方法栈

Java虚拟机栈和本地方法栈的像似,区别在于Java虚拟机为虚拟机执行字节码服务,本地方法栈为虚拟机使用到的Native方法服务。本地按方法栈也可以抛出StackOverflowError异常和OutOfMemmoryError异常。

4.Java堆

Java堆是Java虚拟机分配的最大的一块内存。Java堆被所有线程共享的一块内存,在虚拟机启动时被创建。用于存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域“GC堆”,按照收集器的分代收集方法Java堆还分为:新生代和老年代。再分细点:Eden空间、From Survivor空间、To Survivor空间。Java堆可以是物理上不连续但逻辑上连续的一块内存。

5.方法区

方法区和Java堆一样,是各个线程共享的内存区域,它用于存储被虚拟机加载的信息、常量、静态变量、及时编译器编译后的代码数据等,虽然Java虚拟机吧方法区描述卫队的一个逻辑部分,但是他的一个别名Non-Heap(非堆),目的是为了将它与堆区分开来。

6.运行时常量池

运行时常量池是方法区的一部分。用于存放编译期生成的各种字面量和符号引用。当常量池无法申请到足够内存时抛出OutOfMemmoryError异常

 

二、Java虚拟机对象

1.创建对象

当虚拟机遇到一条new指令时,(1)首先去检查这个指令参数书否能在常量池中找到其符号引用,并检查这个符号引用代表的类是否被加载、解析、初始化过如果没有那就先执行相应的内加载。在类加载检查通过后就将在堆中用“指针碰撞”(内存连续)或“空闲列表”(内存不连续)方式分配空间。(2)初始化,在对象头(Object Header)中初始化信息如:对象是哪个类、怎样找到元数据信对象hush、GC年龄等设置。(3)执行<init>方法。

2.对象的内存分布

在HostSpot虚拟机中,对象在内存中的存储布局分三块区域:对象头、实例数据和对齐填充。

3.对象的使用

对象的使用取决于虚拟机的实现方式目前主要有使用句柄和直接指针两种。

                                                                         使用句柄

                                                                           直接指针

三、几种内存溢出

1.Java堆溢出

Java堆用于存储对象实例,只要不断创建并且避免垃圾回收机制清除这些对象,当对象达到堆得最大容量就会产生溢出现象。

2.虚拟机栈和本地方法栈溢出

1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

2)如果在扩展栈时无法申请到足够的内存空间,将抛出OutOfMemmoryError异常。

结论;新建线程时分配的栈内存越大剩下的内存就越小,在继续创建线程分配栈内存时反而容易产生内存溢出现象。

3.方法区和运行时常量池溢出

可以看到,运行时常量池溢出,在OutOfMemoryError后面的提示信息是“PermGen space”。

四、java虚拟机内存模型

3个问题:

1)哪些内存需要回收?

2)什么时候回收?

3)如何回收?

1.判断内存是否回收

1)引用计数法

给对象添加一个引用计数器,每当有一个地方用它就加1,引用失效时就减1。任何时刻计数器为0的对象是不可能再被使用的。但主流的Java虚拟机没有选用这种方法,其原因主要是因为它很难解决对象之间相互循环引用的问题。

 

如上,虚拟机还是回收了内存,这也证明了虚拟机并没有使用引用计数算法回收内存。

2)可达性分析算法(根搜索法)

通过一系列的称为“GC Root”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用连链“Reference Chain”,当一个对象到GC Roots没用任何引用连相连时,证明斥对象不可用。

3)再谈引用

JDK1.2之后Java对引用的概念进行了扩充:强引用、软引用、弱引用、虚引用。引用强度依次递减。

对象及时在可达性分析算法中视为不可达也还有生存的机会。它的死亡经历两个阶段:当对象没有覆盖finalize()方法或finalize()方法被虚拟机调用过,将没有必要马上回收内存它将会被第一次标记。如果这个对象被判定为有必要执行finalize()方法,它将会被放置到F-Queue队列中它将会第二次标记。如果它还不创建对象关联,那将会被回收,及finalize()方法被调用。

2.垃圾清除方法

首先垃圾回收的主要对象是堆区,少量在方法区和方法区的常量池。而虚拟机栈、本地方法栈和程序计数器随线程共存亡不用Java虚拟机回收。

1)标记清除算法。

如同它的名字,标记清除法分为标记和清除两个阶段。两个弊端1.效率问题,先标记再清除,效率自然降低。2.清除后会留下大量不连续的内存碎片。

2)复制算法

为了解决效率问题,复制算法出现了。它将可用的内存分为两块,每次只使用一块。这块用完了就将还活着的对象复制到另一块内存上,然后再把先前使用的内存一次性清理掉。弊端,内存缩小了原来的一半。

3)标记-整理法

根据老年代的特点,标记-整理算法出现了。让存活的对象都向一端移动,然后直接清理掉端边界意外的内存。

4)分代收集算法

当前商业虚拟机都采用“分代收集”算法,这种算法没什么新思想,只是根据随想存活周期的不同将内存分成几块。

五、垃圾收集器

上图是7种不同的垃圾收集器,连线表示两种垃圾收集器可以协同工作。

1.Serial收集器

Serial别名“Stop World”,单线程。在它执行垃圾回收操作师会在用户看不见的情况下停止所有正在正常运行的所有线程。用户体验不太好,但是即便这样它依然是虚拟机运行在Client模式下的默认新生代收集器。专心做收集工作所以效率高。

2.ParNew收集器

Par New就是Serial收集器的多线程版本。除了多线程为其他和Serial相同,所以它们有很多的相同代码,相比Serial收集器它并么有什么创新之处。

3.Parallel Scavenge收集器

Parallel Scavenge是新生代、多线程的收集器,它可以达到一个可控制的吞吐量。

吞吐量=用户代码运行时间/虚拟机运行时间

(其中虚拟机运行时间=用户代码运行时间+垃圾收集时间)

Parallel Scavenge收集器提供了两个参数用于精准控制吞吐量:①控制最大垃圾收集停顿时间-XX:MaxGCPauseMillis参数 ②直接设置吞吐量大小-XX:GCTimeRatio参数。

4.Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记整理”算法

5.Parallel Old收集器

Praallel Old是Parallel Scavenge收集器的老年代收集器

6.CMS收集器

CMS收集器是一种以获取最短停顿时间为目标的收集器。它是基于“标记-清除”算法实现的具体步骤包括:

1)初始标记

2)并发标记

3)重新标记

4)并发清理

7.G1收集器

G1是一款面向服务器端应用的垃圾收集器。收集范围与其他GC垃圾收集器相比。G1具备如下特点:

1)并行与并发

2)分代收集

3)空间整合

4)可预测的停顿

G1将整个堆划分为等大的(Region),根据允许的收集时间优先收集价值最大的Region,保证在有限时间尽可能的提供收集效率。

8.直接进入老年代的几种情况

1)大对象直接进入老年代

2)长期存活的对象直接进入老年代

 

最后java虚拟机调优常用参数地址如下:

堆大小设置 
JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统 下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。 


典型设置: 
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k 
-Xmx3550m:设置JVM最大可用内存为3550M。 
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。 
-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。 
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行 调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。 

java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0 
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6 
-XX:MaxPermSize=16m:设置持久代大小为16m。 
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。 对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概论。 
回收器选择 
JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认 情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。 
吞吐量优先的并行收集器 
如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。

 

java虚拟机栈分析:

./jstack -l PID >/home/netty-stack.txt

java虚拟机内存分析:

jmap -heap PID >netty-jmp.txt

java虚拟机内存分析:

jmap -histo 25005 >netty-histo.txt

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值