JVM详解

JVM详解
JVM详解

JVM详解

本文详细讲解了JVM(Java Virtual Machine)的方方面面,首先由java的特性来描绘JVM 的大致应用,再细细阐述了JVM的原理及内存管理机制和调优.最后讲述了与JVM密切相关的Java GC机制.

本文内容大多来自网络,但内容十分丰富,是学习JVM的好资料.

后面会再针对JVM的两大职责class loader和execution engine进行讲解

若有疑问

目录

Java相关 (2)

1.1Java定义 (2)

1.2Java的开发流程 (2)

1.3Java运行的原理 (3)

1.4半编译半解释 (4)

1.5平台无关性 (5)

JVM内存模型 (5)

2.1JVM规范 (6)

2.2 Sun JVM (9)

2.3 SUN JVM内存管理(优化) (10)

2.4 SUN JVM调优 (13)

2.5.JVM简单理解 (16)

2.5.1Java栈 (16)

2.5.2堆 (16)

2.5.3堆栈分离的好处 (19)

2.5.4 堆(heap)和栈(stack) (19)

JAVA垃圾收集器 (20)

3.1垃圾收集简史 (20)

3.2常见的垃圾收集策略 (20)

3.2.1Reference Counting(引用计数) (20)

3.2.2跟踪收集器 (21)

3.3JVM的垃圾收集策略 (25)

3.3.1Serial Collector (25)

3.3.2 Parallel Collector (25)

3.3.3 Concurrent Collector (26)

Java虚拟机(JVM)参数配置说明 (26)

Java相关

1.1Java定义

1.2Java的开发流程

1.3Java运行的原理

1.4半编译半解释

1.5平台无关性

JVM内存模型

2.1JVM规范

JVM specification对JVM内存的描述

首先我们来了解JVM specification中的JVM整体架构。如下图:

主要包括两个子系统和两个组件: Class loader(类装载器) 子系统,Execution engine(执行引擎) 子系统;Runtime data area (运行时数据区域)组件, Native interface(本地接口)组件。

Class loader子系统的作用:根据给定的全限定名类名(如

https://www.360docs.net/doc/6212325061.html,ng.Object)来装载class文件的内容到 Runtime data area中的method area(方法区域)。Javsa程序员可以extends https://www.360docs.net/doc/6212325061.html,ng.ClassLoader类来写自己的Class loader。

Execution engine子系统的作用:执行classes中的指令。任何JVM specification实现(JDK)的核心是Execution engine,换句话说:Sun 的JDK 和IBM的JDK好坏主要取决于他们各自实现的Execution engine的好坏。每个运行中的线程都有一个Execution engine的实例。

Native interface组件:与native libraries交互,是其它编程语言交互的接口。

Runtime data area 组件:这个组件就是JVM中的内存。下面对这个部分进行详细介绍。

Runtime data area的整体架构图

Runtime data area 主要包括五个部分:Heap (堆), Method Area(方法区域), Java Stack(java的栈), Program Counter(程序计数器), Native method stack(本地方法栈)。Heap 和Method Area是被所有线程的共享使用的;而Java stack, Program counter 和Native method stack是以线程为粒度的,

每个线程独自拥有。

Heap

Java程序在运行时创建的所有类实或数组都放在同一个堆中。而一个Java虚拟实例中只存在一个堆空间,因此所有线程都将共享这个堆。每一个java程序独占一个JVM实例,因而每个java程序都有它自己的堆空间,它们不会彼此干扰。但是同一java程序的多个线程都共享着同一个堆空间,就得考虑多线程访问对象(堆数据)的同步问题。(这里可能出现的异常https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError: Java heap space)

Method area

在Java虚拟机中,被装载的class的信息存储在Method area的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区。该类型中的类(静态)变量同样也存储在方法区中。与Heap 一样,method area是多线程共享的,因此要考虑多线程访问的同步问题。比如,假设同时两个线程都企图访问一个名为Lava的类,而这个类还没有内装载入虚拟机,那么,这时应该只有一个线程去装载它,而另一个线程则只能等待。(这里可能出现的异常https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError: PermGen full)

Java stack

Java stack以帧为单位保存线程的运行状态。虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈。每当线程调用一个方法的时候,就对当前状态作为一个帧保存到java stack中(压栈);当一个方法调用返回时,从java stack弹出一个帧(出栈)。栈的大小是有一定的限制,这个可能出现StackOverFlow问题。下面的程序可以说明这个问题。

public class TestStackOverFlow {

public static void main(String[] args) {

Recursive r = new Recursive();

r.doit(10000);

// Exception in thread "main"

https://www.360docs.net/doc/6212325061.html,ng.StackOverflowError

}

}

class Recursive {

public int doit(int t) {

if (t <= 1) {

return 1;

}

return t + doit(t - 1);

}

}

Program counter

每个运行中的Java程序,每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的饿“地址”,这里的“地址”可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。

Native method stack

对于一个运行中的Java程序而言,它还能会用到一些跟本地方法相关的数据区。当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,不止如此,它还可以做任何它想做的事情。比如,可以调用寄存器,或在操作系统中分配内存等。总之,本地方法具有和JVM相同的能力和权限。(这里出现JVM无法控制的内存溢出问题native heap OutOfMemory )

2.2 Sun JVM

Sun JVM中对JVM Specification的实现(内存部分)JVM Specification只是抽象的说明了JVM实例按照子系统、内存区、数据类型以及指令这几个术语来描述的,但是规范并非是要强制规定Java虚拟机实现内部的体系结构,更多的是为了严格地定义这些实现的外部特征。

Sun JVM实现中:Runtime data area(JVM 内存) 五个部分中的Java Stack , Program Counter, Native method stack三部分和规范中的描述基本一致;但对Heap 和Method Area进行了自己独特的实现。这个实现和Sun JVM 的Garbage collector(垃圾回收)机制有关,下面的章节进行详细描述。

垃圾分代回收算法(Generational Collecting)

基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

如上图所示,为Java堆中的各代分布。

1. Young(年轻代)JVM specification中的Heap的一部份

年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor 区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

2. Tenured(年老代)JVM specification中的Heap的一部份

年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。3. Perm(持久代)JVM specification中的Method area

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

2.3 SUN JVM内存管理(优化)

在我做J2EE系统开发的工作生涯中,经常遇到技术人员或客户发出诸如此类的感慨:我的J2EE应用系统处理的数据量不大,系统体积也不大,技术架构也没有问题,我的应用服务器的内存有4G或8G;系统运行起来很慢,还经常出现内存溢出错误。真是无奈!每次遇到这样的情况,我心中都会忍不住窃笑之。

其实他们所遇到这种情况,不是技术架构上的问题,不是系统本身的问题,也不是应用服务器的问题,也可能不是服务器的内存资源真的不足的问题。他们花了很多时间在J2EE应用系统本身上找问题(当然一般情况下,这种做法是对的;当出现问题时,在自身上多找找有什么不足),结果还是解决不了问题。他们却忽略了很重要的一点:J2EE应用系统是运行在J2EE应用服务器上的,而J2EE 应用服务器又是运行在JVM(Java Virtual Machine)上的。

其实在生产环境中JVM参数的优化和设置对J2EE应用系统性能有着决定性的作用。本篇我们就来分析JAVA的创建者SUN 公司的JVM的内存管理机制(在现实中绝大多数的应用服务器是运行在SUN公司的JVM上的,当然除了SUN公司的JVM,还有IBM的JVM,Bea的JVM等);下篇咱们具体讲解怎样优化JVM的参数以达到优化J2EE应用的目的。

咱们先来看JVM的内存管理制吧,JVM的早期版本并没有进行分区管理;这样的后果是JVM进行垃圾回收时,不得不扫描JVM所管理的整片内存,所以搜集垃圾是很耗费资源的事情,也是早期JAVA程序的性能低下的主要原因。随着JVM的发展,JVM引进了分区管理的机制。

采用分区管理机制的JVM将JVM所管理的所有内存资源分为2个大的部分。永久存储区(Permanent Space)和堆空间(The Heap Space)。其中堆空间又分为新生区(Young (New) generation space)和养老区(Tenure (Old) generation space),新生区又分为伊甸园(Eden space),幸存者0区(Survivor 0 space)和幸存者1区(Survivor 1 space)。具体分区如下图:

那JVM他的这些分区各有什么用途,请看下面的解说。

永久存储区(Permanent Space):永久存储区是JVM的驻留内存,用于存放JDK 自身所携带的Class,Interface的元数据,应用服务器允许必须的

Class,Interface的元数据和Java程序运行时需要的Class和Interface的元数据。被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM时,释放此区域所控制的内存。

堆空间(The Heap Space):是JAVA对象生死存亡的地区,JAVA对象的出生,成长,死亡都在这个区域完成。堆空间又分别按JAVA对象的创建和年龄特征分为养老区和新生区。

新生区(Young (New) generation space):新生区的作用包括JAVA对象的创建和从JAVA对象中筛选出能进入养老区的JAVA对象。

伊甸园(Eden space):JAVA对空间中的所有对象在此出生,该区的名字因此而得名。也即是说当你的JAVA程序运行时,需要创建新的对象,JVM将在该区为你创建一个指定的对象供程序使用。创建对象的依据即是永久存储区中的元数据。

幸存者0区(Survivor 0 space)和幸存者1区(Survivor1 space):当伊甸园的空间用完时,程序又需要创建对象;此时JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁工作。同时将伊甸园中的还有其他对象引用的对象移动到幸存者0区。幸存者0区就是用于存放伊甸园垃圾回收时所幸存下来的JAVA对象。当将伊甸园中的还有其他对象引用的对象移动到幸存者0区时,如果幸存者0区也没有空间来存放这些对象时,JVM的垃圾回收器将对幸存者0区进行垃圾回收处理,将幸存者0区中不在有其他对象引用的JAVA对象进行销毁,将幸存者0区中还有其他对象引用的对象移动到幸存者1区。幸存者1区的作用就是用于存放幸存者0区垃圾回收处理所幸存下来的JAVA对象。

养老区(Tenure (Old) generation space):用于保存从新生区筛选出来的JAVA 对象。

上面我们看了JVM的内存分区管理,现在我们来看JVM的垃圾回收工作是怎样运作的。首先当启动J2EE应用服务器时,JVM随之启动,并将JDK的类和接口,应用服务器运行时需要的类和接口以及J2EE应用的类和接口定义文件也及编译后的Class文件或JAR包中的Class文件装载到JVM的永久存储区。在伊甸园中创建JVM,应用服务器运行时必须的JAVA对象,创建J2EE应用启动时必须创建的JAVA对象;J2EE应用启动完毕,可对外提供服务。

JVM在伊甸园区根据用户的每次请求创建相应的JAVA对象,当伊甸园的空间不足以用来创建新JAVA对象的时候,JVM的垃圾回收器执行对伊甸园区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没

有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到幸存者0区。

如果幸存者0区有足够空间存放则直接放到幸存者0区;如果幸存者0区没有足够空间存放,则JVM的垃圾回收器执行对幸存者0区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到幸存者1区。

如果幸存者1区有足够空间存放则直接放到幸存者1区;如果幸存者0区没有足够空间存放,则JVM的垃圾回收器执行对幸存者0区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并将那些被其他对象所引用的JAVA对象移动到养老区。

如果养老区有足够空间存放则直接放到养老区;如果养老区没有足够空间存放,则JVM的垃圾回收器执行对养老区区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象(如果该对象仅仅被一个没有其他对象引用的对象引用的话,此对象也被归为没有存在的必要,依此类推),并保留那些被其他对象所引用的JAVA对象。如果到最后养老区,幸存者1区,幸存者0区和伊甸园区都没有空间的话,则JVM会报告“JVM堆空间溢出(https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError: Java heap space)”,也即是在堆空间没有空间来创建对象。

这就是JVM的内存分区管理,相比不分区来说;一般情况下,垃圾回收的速度要快很多;因为在没有必要的时候不用扫描整片内存而节省了大量时间。

通常大家还会遇到另外一种内存溢出错误“永久存储区溢出

(https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError: Java Permanent Space)”。

好,本篇对SUN 的JVM内存管理机制讲解就到此为止,下一篇我们将详细讲解怎样优化SUN 的JVM让我们的J2EE系统运行更快,不出现内存溢出等问题。

2.4 SUN JVM调优

JVM相关参数:

参数名参数说明

-server 启用能够执行优化的编译器, 显著提高服务器的性能,但使用能够执行优化的编译器时,服务器的预备时间将会较长。生产环境的服务器强烈推荐设置此参数。

-Xss 单个线程堆栈大小值;JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在

3000~5000左右。

-XX:+UseParNewGC 可用来设置年轻代为并发收集【多CPU】,如果你的服务器有多个CPU,你可以开启此参数;开启此参数,多个CPU可并发进行垃圾回收,可提高垃圾回收的速度。此参数和+UseParallelGC,-XX:ParallelGCThreads搭配使用。

+UseParallelGC 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。可提高系统的

吞吐量。

-XX:ParallelGCThreads 年轻代并行垃圾收集的前提下(对并发也有效果)的线程数,增加并行度,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

永久存储区相关参数:

参数名参数说明

-Xnoclassgc 每次永久存储区满了后一般GC算法在做扩展分配内存前都会触发一次FULL GC,除非设置了-Xnoclassgc.

-XX:PermSize 应用服务器启动时,永久存储区的初始内存大

-XX:MaxPermSize 应用运行中,永久存储区的极限值。为了不消耗扩大JVM永久存储区分配的开销,将此参数和-XX:PermSize这个两个值设为相等。

堆空间相关参数

参数名参数说明

-Xms 启动应用时,JVM堆空间的初始大小值。

-Xmx 应用运行中,JVM堆空间的极限值。为了不消耗扩大JVM堆空间分配的开销,将此参数和-Xms这个两个值设为相等,考虑到需要开线程,将此值设置为总内存的80%.

-Xmn 此参数硬性规定堆空间的新生代空间大小,推荐设为堆空间大小的1/4。

上面所列的JVM参数关系到系统的性能,而其中-XX:PermSize,

-XX:MaxPermSize,-Xms,-Xmx和-Xmn这5个参数更是直接关系到系统的性能,系统是否会出现内存溢出。

-XX:PermSize和-XX:MaxPermSize分别设置应用服务器启动时,永久存储区的初始大小和极限大小;在生成环境中强烈推荐将这个两个值设置为相同的值,以避免分配永久存储区的开销,具体的值可取系统“疲劳测试”获取到的永久存储区的极限值;如果不进行设置-XX:MaxPermSize默认值为64M,一般来说系统的类定义文件大小都会超过这个默认值。

-Xms和-Xmx分别是服务器启动时,堆空间的初始大小和极限值。-Xms的默认值是物理内存的1/64但小于1G,-Xmx的默认值是物理内存的1/4但小于1G.在生产环境中这些默认值是肯定不能满足我们的需要的。也就是你的服务器有8g的内存,不对JVM参数进行设置优化,应用服务器启动时还是按默认值来分配和约束JVM对内存资源的使用,不会充分的利用所有的内存资源。

到此我们就不难理解上文提到的“我的服务器有8g内存,系统也就100M左右,居然出现内存溢出”这个“怪现象”了。在上文我曾提到“永久存储区溢出(https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError: Java Permanent Space)”和“JVM堆空间溢出(https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError: Java heap space)”这两种溢出错误。现在大家都知道答案了:“永久存储区溢出(https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError: Java Permanent Space)”乃是永久存储区设置太小,不能满足系统需要的大小,此时只需要调整-XX:PermSize和-XX:MaxPermSize这两个参数即可。“JVM堆空间溢出(https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError: Java heap space)”错误是JVM堆空间不足,此时只需要调整-Xms和-Xmx这两个参数即可。

到此我们知道了,当系统出现内存溢出时,是哪些参数设置不合理需要调整。但

我们怎么知道服务器启动时,到底JVM内存相关参数的值是多少呢。在实践中,经常遇到对JVM参数进行设置了,并且自己心里觉得应该不会出现内存溢出了;但不幸的是内存溢出还是发生了。很多人百思不得其解,那我可以肯定地告诉你,你设置的JVM参数并没有起作用(本文咱不探讨没有起作用的原因)。不信我们就去看看,下面介绍如何使用SUN公司的内存使用监控工具jvmstat.

本文只介绍如何使用jvmstat查看内存使用,不介绍其安装配置。有兴趣的读者,可到SUN公司的官方网站下载一个,他本身已经带有非常详细的安装配置文档了。这里假设你已经在你的应用服务器上配置好了jvmstat了。那我们就开始使用他来看看我们的服务器到底是有没有按照我们设置的参数启动。

首先启动服务器,等服务器启动完。开启DOS窗口(此例子是在windows下完成,linux下同样),在dos窗口中输入jps这个命令。如下图

窗口中会显示所有JAVA应用进程列表,列表的第一列为应用的进程ID,第二列为应用的名字。在列表中找到你的应用服务器的进程ID,比如我这里的应用服务器进程ID为1856.在命令行输入visualgc 1856回车。进入jvmstat的主界面,如下图:

上图分别标注了伊甸园,幸存者0区,幸存者1区,养老区和永久存储区。图上直观的反应出各存储区的大小,已经使用的大小,剩下的空间大小,并用数字标出了各区的大小;如果你这上面的数字和你设置的JVM参数相同的话,那么恭喜你,你设置的参数已经起作用,如果和你设置的不一致的话,那么你设置的参数没有起作用(可能是服务器的启动方式没有载入你的JVM参数设置。)

在优化服务器的时候,这个工具很有用,他占用资源少。可以随应用服务器一直保持开启状态,如果系统发生内粗溢出,可以一眼就看出是那个区发生了溢出。根据观察结果进行进一步优化。

2.5.JVM简单理解

2.5.1Java栈

Java栈是与每一个线程关联的,JVM在创建每一个线程的时候,会分配一定的栈空间给线程。它主要用来存储线程执行过程中的局部变量,方法的返回值,以及方法调用上下文。栈空间随着线程的终止而释放。

StackOverflowError:如果在线程执行的过程中,栈空间不够用,那么JVM 就会抛出此异常,这种情况一般是死递归造成的。

2.5.2堆

Java中堆是由所有的线程共享的一块内存区域,堆用来保存各种JAVA对象,比如数组,线程对象等。

2.5.2.1Generation

JVM堆一般又可以分为以下三部分:

?Perm

Perm代主要保存class,method,filed对象,这部分的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到https://www.360docs.net/doc/6212325061.html,ng.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。

?Tenured

Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。

?Young

Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Young区间变满的时候,minor GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。

2.5.2.2Sizing the Generations

JVM提供了相应的参数来对内存大小进行配置。

正如上面描述,JVM中堆被分为了3个大的区间,同时JVM也提供了一些选项对Young,Tenured的大小进行控制。

?Total Heap

-Xms:指定了JVM初始启动以后初始化内存

-Xmx:指定JVM堆得最大内存,在JVM启动以后,会分配-Xmx参数指定大小的内存给JVM,但是不一定全部使用,JVM会根据-Xms参数来调节真正用于JVM的内存

-Xmx-Xms之差就是三个Virtual空间的大小

?Young Generation

-XX:NewRatio=8意味着tenured 和young的比值8:1,这样eden+2*survivor=1/9

堆内存

-XX:SurvivorRatio=32意味着eden和一个survivor的比值是32:1,这样一个Survivor就占Young区的1/34.

-Xmn参数设置了年轻代的大小

?Perm Generation

-XX:PermSize=16M -XX:MaxPermSize=64M

Thread Stack

-XX:Xss=128K

2.5.3堆栈分离的好处

呵呵,其它的先不说了,就来说说面向对象的设计吧,当然除了面向对象的设计带来的维护性,复用性和扩展性方面的好处外,我们看看面向对象如何巧妙的利用了堆栈分离。如果从JAVA内存模型的角度去理解面向对象的设计,我们就会发现对象它完美的表示了堆和栈,对象的数据放在堆中,而我们编写的那些方法一般都是运行在栈中,因此面向对象的设计是一种非常完美的设计方式,它完美的统一了数据存储和运行。

2.5.4 堆(heap)和栈(stack)

什么叫堆?你用十几个麻将牌竖直叠成一摞这叫堆,你可以从上面、下面、中间任意抽出一张牌,也可以任意插入一张。

什么叫栈?AK-47的弹匣就是一个栈,在上面的子弹没被取出之前,你无法取出下面的子弹——尽管你可以从边上的透明部分读出里面装的是什么型号、颜色的子弹。

堆很灵活,但是不安全。对于对象,我们要动态地创建、销毁,不能说后创建的对象没有销毁,先前创建的对象就不能销毁,那样的话我们的程序就寸步难行,所以Java中用堆来存储对象。而一旦堆中的对象被销毁,我们继续引用这个对象的话,就会出现著名的NullPointerException,这就是堆的缺点——错误的引用逻辑只有在运行时才会被发现。

栈不灵活,但是很严格,是安全的,易于管理。因为只要上面的引用没有销毁,下面引用就一定还在,所以,在栈中,上面引用永远可以通过下面引用来查找对象,同时如果确认某一区间的内容会一起存在、一起销毁,也可以上下互相引用。在大部分程序中,都是先定义的变量、引用先进栈,后定义的后进栈,同时,区块内部的变量、引用在进入区块时压栈,区块结束时出栈,理解了这种机制,我们就可以很方便地理解各种编程语言的作用域的概念了,同时这也是栈的优点——错误的引用逻辑在编译时就可以被发现。

举例说明

简单的说其实栈就是存放变量引用的一个地方,堆就是存放实际对象的地方也就是.

比如:int i = 7; 这个其实是存在栈里边的。内容为i = 7。

Apple app = new Apple(); 这个app 是在栈里边的他对应的是一个内存地址也在堆里边,

而这个内存地址对应的是堆里边存放Apple 实例的地址。

String s = "Hello World!"; 这个其实是存在另外一块静态代码区。

总体来说:栈--主要存放引用和基本数据类型。

堆--用来存放new 出来的对象实例。

JAVA垃圾收集器

3.1垃圾收集简史

垃圾收集提供了内存管理的机制,使得应用程序不需要在关注内存如何释放,内存用完后,垃圾收集会进行收集,这样就减轻了因为人为的管理内存而造成的错误,比如在C++语言里,出现内存泄露时很常见的。

Java语言是目前使用最多的依赖于垃圾收集器的语言,但是垃圾收集器策略从20世纪60年代就已经流行起来了,比如Smalltalk,Eiffel等编程语言也集成了垃圾收集器的机制。

3.2常见的垃圾收集策略

所有的垃圾收集算法都面临同一个问题,那就是找出应用程序不可到达的内存块,将其释放,这里面得不可到达主要是指应用程序已经没有内存块的引用了,而在JAVA中,某个对象对应用程序是可到达的是指:这个对象被根(根主要是指类的静态变量,或者活跃在所有线程栈的对象的引用)引用或者对象被另一个可到达的对象引用。

3.2.1Reference Counting(引用计数)

引用计数是最简单直接的一种方式,这种方式在每一个对象中增加一个引用的计数,这个计数代表当前程序有多少个引用引用了此对象,如果此对象的引用计数变为0,那么此对象就可以作为垃圾收集器的目标对象来收集。

优点:

简单,直接,不需要暂停整个应用

缺点:

JVM原理以及JVM内存管理机制

一、 JVM简介 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成, 首先来说一下JVM工作原理中的jdk这个东西, .JVM 在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机. 操作系统装入JVM是通过jdk中Java.exe来完成。 通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例 4.调用JNIEnv实例装载并处理class类。 对于JVM自身的物理结构,我们可以从下图了解:

JVM的一个重要的特征就是它的自动内存管理机制,在执行一段Java代码的时候,会把它所管理的内存划分 成几个不同的数据区域,其中包括: 1. 程序计数器,众所周知,JVM的多线程是通过线程轮流切换并 分配CPU执行时间的方式来实现的,那么每一个线程在切换 后都必须记住它所执行的字节码的行号,以便线程在得到CPU 时间时进行恢复,这个计数器用于记录正在执行的字节码指令的地址,这里要强调的是“字节码”,如果执行的是Native方法,那么这个计数器应该为null; 2.

3. Java计算栈,可以说整个Java程序的执行就是一个出栈入栈 的过程,JVM会为每一个线程创建一个计算栈,用于记录线程中方法的调用和变量的创建,由于在计算栈里分配的内存出栈后立即被抛弃,因此在计算栈里不存在垃圾回收,如果线程请求的栈深度大于JVM允许的深度,会抛出StackOverflowError 异常,在内存耗尽时会抛出OutOfMemoryError异常; 4. Native方法栈,JVM在调用操作系统本地方法的时候会使用到 这个栈; 5. Java堆,由于每个线程分配到的计算栈容量有限,对于可能会 占据大量内存的对象,则会被分配到Java堆中,在栈中包含了指向该对象内存的地址;对于一个Java程序来说,只有一个Java堆,也就是说,所有线程共享一个堆中的对象;由于Java堆不受线程的控制,如果在一个方法结束之后立即回收这个方法使用到的对象,并不能保证其他线程是否正在使用该对象;因此堆中对象的回收由JVM的垃圾收集器统一管理,和某一个线程无关;在HotSpot虚拟机中Java堆被划分为三代:o新生代,正常情况下新创建的对象会被分配到新生代,但如果对象占据的内存足够大以致超过了新生代的容量限 制,也可能被分配到老年代;新生代对象的一个特点是最 新、且生命周期不长,被回收的可能性高;

JVM调优总结

JVM调优总结(一)-- 一些概念 数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。―引用值‖代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。 基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress 引用类型包括:类类型,接口类型和数组。 堆与栈 堆和栈是程序运行的关键,很有必要把他们的关系说清楚。 栈是运行时的单位,而堆是存储的单位。 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。

在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。 为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗? 第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。 第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。 第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。 第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。 在Java中,Main函数就是栈的起始点,也是程序的起始点。 程序要运行总是有一个起点的。同C语言一样,java中的Main就是那个起点。无论什么java程序,找到main就找到了程序执行的入口:) 堆中存什么?栈中存什么? 堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:))。 为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java中参数传递时的问题。

Java之volatile的使用及其原理

一、volatile的作用 我们已经知道可见性、有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果对Synchronized原理有了解的话,应该知道Synchronized是一个比较重量级的操作,对系统的性能有比较大的影响,所以,如果有其他解决方案,我们通常都避免使用Synchronized来解决问题。 而volatile关键字就是Java中提供的另一种解决可见性和有序性问题的方案。对于原子性,需要强调一点,也是大家容易误解的一点:对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。 二、volatile的使用 关于volatile的使用,我们可以通过几个例子来说明其使用方式和场景。 1、防止重排序 我们从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。其源码如下: package com.paddx.test.concurrent; public class Singleton { public static volatile Singleton singleton; /** * 构造函数私有,禁止外部实例化 */ private Singleton() {}; public static Singleton getInstance() { if (singleton == null) { synchronized (singleton) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 现在我们分析一下为什么要在变量singleton之间加上volatile关键字。要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤: ?分配内存空间。 ?初始化对象。

java内存屏障与JVM并发详解

深入Java底层:内存屏障与JVM并发详解(1) 本文介绍了内存屏障对多线程程序的影响,同时将研究内存屏障与JVM并发机制的关系,如易变量(volatile)、同步(synchronized)和原子条件式(atomic conditional)。 AD:内存屏障,又称内存栅栏,是一组处理器指令,用于实现对内存操作的顺序限制。本文假定读者已经充分掌握了相关概念和Java内存模型,不讨论并发互斥、并行机制和原子性。内存屏障用来实现并发编程中称为可见性(visibility)的同样重要的作用。 关于JVM更多内容,请参阅:JVM详解 Java虚拟机原理与优化 内存屏障为何重要? 对主存的一次访问一般花费硬件的数百次时钟周期。处理器通过缓存(caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存操作的顺序。也就是说,程序的读写操作不一定会按照它要求处理器的顺序执行。当数据是不可变的,同时/或者数据限制在线程范围内,这些优化是无害的。 如果把这些优化与对称多处理(symmetric multi-processing)和共享可变状态(shared mutable state)结合,那么就是一场噩梦。当基于共享可变状态的内存操作被重新排序时,程序可能行为不定。一个线程写入的数据可能被其他线程可见,原因是数据写入的顺序不一致。适当的放置内存屏障通过强制处理器顺序执行待定的内存操作来避免这个问题。 内存屏障的协调作用 内存屏障不直接由JVM暴露,相反它们被JVM插入到指令序列中以维持语言层并发原语的语义。我们研究几个简单Java程序的源代码和汇编指令。首先快速看一下Dekker算法中的内存屏障。该算法利用volatile变量协调两个线程之间的共享资源访问。 请不要关注该算法的出色细节。哪些部分是相关的?每个线程通过发信号试图进入代码第一行的关键区域。如果线程在第三行意识到冲突(两个线程都要访问),通过turn变量的操作来解决。在任何时刻只有一个线程可以访问关键区域。 1. // code run by first thread // code run by second thread 2. 3. 1 intentFirst = true; intentSecond = true;

Java虚拟机工作原理(JVM)

As the Java V irtual Machine is a stack-based machine, almost all of its instructions involve the operand stack in some way. Most instructions push values, pop values, or both as they perform their functions. Java虚拟机是基于栈的(stack-based machine)。几乎所有的java虚拟机的指令,都与操作数栈(operand stack)有关.绝大多数指令都会在执行自己功能的时候进行入栈、出栈操作。 1Java体系结构介绍 Javaís architecture arises out of four distinct but interrelated technologies, each of which is defined by a separate specification from Sun Microsystems: 1.1 Java体系结构包括哪几部分? Java体系结构包括4个独立但相关的技术 the Java programming language →程序设计语言 the Java class file format →字节码文件格式 the Java Application Programming Interface→应用编程接口 the Java V irtual Machine →虚拟机 1.2 什么是JVM java虚拟机和java API组成了java运行时。 1.3 JVM的主要任务。 Java虚拟机的主要任务是装载class文件并执行其中的字节码。 Java虚拟机包含了一个类装载器。 类装载器的体系结构 二种类装载器 启动类装载器 用户定义的类装载器 启动类装载器是JVM实现的一部分 当被装载的类引用另外一个类时,JVM就是使用装载第一个类的类装载器装载被引用的类。 1.4 为什么java容易被反编译? ●因为java程序是动态连接的。从一个类到另一个类的引用是符号化的。在静态连接的 可执行程序中。类之间的引用只是直接的指针或者偏移量。相反在java的class文件中,指向另一个类的引用通过字符串清楚的标明了所指向的这个类的名字。

JVM调优总结:典型配置举例

JVM调优总结:典型配置举例 以下配置主要针对分代垃圾回收算法而言。 堆大小设置 年轻代的设置很关键 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给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默

JVM工作原理

JVM工作原理 1.JVM是什么? 为java程序提供运行环境,将java字节码文件翻译成机器可执行的二进制程序。 2. JVM装入:操作系统通过jdk中的java.exe来装入JVM ①、创建JVM装载环境和配置 ②、装载JVM.dll ③、初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例 ④、调用JNIEnv实例装载并处理class类 3. JVM装入环境,JVM提供的方式是操作系统的动态连接文件 基于Windows的实现的分析 ①、查找jre路径 Java是通过GetApplicationHome api来获得当前的Java.exe绝对路径 ②、查找JVM.dll F:\Java\jdk1.6.0_20\jre\bin\java.dll 或GetPublicJREHome HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment ③、装载JVM.cfg文件 F:\Java\jdk1.6.0_20\jre\lib\i386\JVM.cfg jdk目录中jre\bin\server和jre\bin\client都有JVM.dll文件存在,而Java 正是通过JVM.cfg配置文件来管理这些不同版本的JVM.dll的 主要参数: -client KNOWN -server KNOWN -hotspot ALIASED_TO -client -classic WARN -native ERROR -green ERROR KNOWN表示JVM存在ALIASED_TO表示给别的JVM取一个别名 WARN表示不存在时找一个JVM替代ERROR表示不存在抛出异常 在运行Java XXX是,Java.exe会通过CheckJVMType来检查当前的JVM类型,Java可以通过两种参数的方式来指定具体的JVM类型,一种按照JVM.cfg文件中的JVM名称指定,第二种方法是直接指定: ①、Java –J ②、Java -XXaltJVM= 或Java -J-XXaltJVM= 如果是第一种参数传递方式,CheckJVMType函数会取参数…-J?后面的JVM名称,然后从已知的JVM配置参数中查找如果找到同名的则去掉该JVM名称前的…-?直接返回该值;而第二种方法,会直接返回“-XXaltJVM=”或“-J-XXaltJVM=”后面的JVM类型名称。如果在运行Java时未指定上面两种方法中的任一一种参数,CheckJVMType会取配置文件中

Java架构学习【JVM与性能优化知识点整理】编写高效优雅Java程序

面向对象 构造器参数太多怎么办? 用builder模式,用在 1、5个或者5个以上的成员变量 2、参数不多,但是在未来,参数会增加 Builder模式: 属于对象的创建模式,一般有 1.抽象建造者:一般来说是个接口,包含1)建造方法,建造部件的方法(不止一 个),2)返回产品的方法 2.具体建造者 3.导演者,调用具体的建造者,创建产品对象 4.产品,需要建造的复杂对象 对于客户端,创建导演者和具体建造者,并把具体建造者交给导演者,然后由客户端通知导演者操纵建造者进行产品的创建。 在实际的应用过程中,有时会省略抽象建造者和导演者。 不需要实例化的类应该构造器私有 如,一些工具类提供的都是静态方法,这些类是不应该提供具体的实例的。可以参考JDK 中的Arrays。 不要创建不必要的对象 1.避免无意中创建的对象,如自动装箱 2.可以在类的多个实例之间重用的成员变量,尽量使用static。

但是,要记住,是不要创建不必要的对象,而不是不要创建对象。 对象池要谨慎使用,除非创建的对象是非常昂贵的操作,如数据库的连接,巨型对象等等。 避免使用终结方法 finalizer方法,jdk不能保证何时执行,也不能保证一定会执行。如果有确实要释放的资源应该用try/finally。 使类和成员的可访问性最小化 编写程序和设计架构,最重要的目标之一就是模块之间的解耦。使类和成员的可访问性最小化无疑是有效的途径之一。 使可变性最小化 尽量使类不可变,不可变的类比可变的类更加易于设计、实现和使用,而且更不容易出错,更安全。 常用的手段: 不提供任何可以修改对象状态的方法; 使所有的域都是final的。 使所有的域都是私有的。 使用写时复制机制。带来的问题:会导致系统产生大量的对象,而且性能有一定的影响,需要在使用过程中小心权衡。 复合优先于继承 继承容易破坏封装性,而且会使子类的实现依赖于父类。 复合则是在类中增加一个私有域,引用类的一个实例,这样的话就避免了依赖类的具体实现。 只有在子类确实是父类的一个子类型时,才比较适合用继承。 接口优于抽象类 java是个单继承的,但是类允许实现多个接口。

synchronized和LOCK的实现原理---深入JVM锁机制--比较好

JVM底层又是如何实现synchronized的? 目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea。本文并不比较synchronized与Lock 孰优孰劣,只是介绍二者的实现原理。 数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令,大家可能会进一步追问:JVM底层又是如何实现synchronized的? 本文所指说的JVM是指Hotspot的6u23版本,下面首先介绍synchronized的实现: synrhronized关键字简洁、清晰、语义明确,因此即使有了Lock接口,使用的还是非常广泛。其应用层的语义是可以把任何一个非null对象作为"锁",当synchronized作用在方法上时,锁住的便是对象实例(this);当作用在静态方法时锁住的便是对象对应的Class实例,因为Class数据存在于永久带,因此静态方法锁相当于该类的一个全局锁;当synchronized作用于某一个对象实例时,锁住的便是对应的代码块。在HotSpot JVM实现中,锁有个专门的名字:对象监视器。 1. 线程状态及状态转换 当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程: Contention List:所有请求锁的线程将被首先放置到该竞争队列 Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck Owner:获得锁的线程称为Owner !Owner:释放锁的线程 下图反映了个状态转换关系:

JVM性能调优

JVM性能调优 一、引言 本文的读者是技术支持人员。阅读本文后,你将理解jboss的启动脚本文件(run.sh)中有一系列的JVM配置参数的含义,以及如何调整它们,从而使得MegaEyes中心管理服务器的性能得到优化。 MegaEyes中心管理服务器的性能调优涉及到系统的多个方面,包括MegaEyes应用本身、应用服务器(jboss)、数据库和java虚拟机(JVM)等等。本文重点介绍JVM的性能优化。 需要注意的是,JVM性能调优具有应用独特性(application specific),就是说,不同的应用情形应该有不同的调整方案,这就要求你首先要观察JVM的运行状态,然后根据观察结果调整参数。没有一个通用的调优方案可以适用于所有的MegaEyes应用。 什么是性能调优 对性能调优,不同的人有不同的理解,本文是指对下列指标最大化: ?并发用户(concurrent users),在服务请求失败或请求响应超过预期时间之前,系 统支持的最大并发用户数量。 ?系统容量(throughput),可以用每秒处理的事务(transaction)数量计算。 ?可靠性(reliability) 换句话说,我们想对更多的用户提供更快捷的、不会中断的服务。 JVM性能调优的重点 JVM的性能调优的重点是垃圾回收(gc,garbage collection)和内存管理。垃圾回收的时候会导致整个虚拟机暂停服务,因此,应该尽可能地缩短垃圾回收的处理时间。 JVM内存 JVM占用的内存称为堆(heap),它被分为三个区:年轻(young,又称为new)、老(tenured,又称为old)和永生(perm)。这三个区是按照java对象的生存期划分的,在new区的对象生存期最短,很快就会被gc回收;perm区的对象生存期最长,与JVM同生死。Perm区的对象不会被gc回收。 new区又被分为三个部分:伊甸园(eden)和两个幸存者(survivor)。对象的创建总是在eden部分(这大概就是命名该部分为eden的原因吧)。两个survivor中总有一个是空的,它作为另一个survivor的缓冲区。当gc发生时,所有eden和survivor中活下来的对象被移动到另一个survivor中。对象会在两个survivor之间不断移动,直到活得足够久,然后移动到old区。我们可以猜想,之所以如此划分使用内存,肯定是为了缩短gc的执行时间,提

java序列化原理与算法

Java序列化原理和算法 总结: java中序列化算法的顺序: 1、子类的类信息描述 2、子类的字段信息描述(如果遇到类对象的属性,暂时用string的指针表示) 3、父类的类信息描述 4、父类的字段信息描述 5、对父类的字段信息进行赋值 6、对子类的字段信息进行赋值 7、发现子类的字段为类对象时,描述该类对象,并查找该类对象的父类,以以上方式描述,然后赋值。 本文讲解了Java序列化的机制和原理。从文中你可以了解如何序列化一个对象,什么时候需要序列化以及Java序列化的算法。 有关Java对象的序列化和反序列化也算是Java基础的一部分,下面对Java序列化的机制和原理进行一些介绍。 Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization 是一种将这些字节重建成一个对象的过程。Java序列化API提供一种处理对象序列化的标准机制。在这里你能学到如何序列化一个对象,什么时候需要序列化以及Java序列化的算法,我们用一个实例来示范序列化以后的字节是如何描述一个对象的信息的。 序列化的必要性 Java中,一切都是对象,在分布式环境中经常需要将Object从这一端网络或设备传递到另一端。 这就需要有一种可以在两端传输数据的协议。Java序列化机制就是为了解决这个问题而产生。如何序列化一个对象 一个对象能够序列化的前提是实现Serializable接口,Serializable接口没有方法,更像是个标记。 有了这个标记的Class就能被序列化机制处理。 import java.io.Serializable; class TestSerial implements Serializable { public byte version = 100; public byte count = 0; } 然后我们写个程序将对象序列化并输出。ObjectOutputStream能把Object输出成Byte流。 我们将Byte流暂时存储到temp.out文件里。 public static void main(String args[]) throws IOException { FileOutputStream fos = new FileOutputStream("temp.out");

JVM调优

一、 GC概要: JVM 堆相关知识 为什么先说JVM堆? JVM的堆是Java对象的活动空间,程序中的类的对象从中分配空间,其存储着正在运行着的应用程序用到的所有对象。这些对象的建立方式就是那些new一类的操作,当对象无用后,是GC来负责这个无用的对象(地球人都知道)。 JVM堆 (1) 新域:存储所有新成生的对象 (2) 旧域:新域中的对象,经过了一定次数的GC循环后,被移入旧域 (3)永久域:存储类和方法对象,从配置的角度看,这个域是独立的,不包括在JVM堆内。默认为4M。 新域会被分为3个部分:1.第一个部分叫Eden。(伊甸园??可能是因为亚当和夏娃是人类最早的活动对象?)2.另两个部分称为辅助生存空间(幼儿园),我这里一个称为A空间(From sqace),一个称为B空间(To Space)。

GC 浅谈: GC的工作目的很明确:在堆中,找到已经无用的对象,并把这些对象占用的空间收回使其可以重新利用.大多数垃圾回收的算法思路都是一致的:把所有对象组成一个集合,或可以理解为树状结构,从树根开始找,只要可以找到的都是活动对象,如果找不到,这个对象就是凋零的昨日黄花,应该被回收了。 在sun 的文档说明中,对JVM堆的新域,是采用coping算法,该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成一个对象面和多个空闲面,程序从对象面为对象分配空间,当对象满了,基于 coping算法的垃圾收集就从根集中扫描活动对象,并将每个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲

洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。 对于新生成的对象,都放在Eden中;当Eden充满时(小孩太多了),GC将开始工作,首先停止应用程序 的运行,开始收集垃圾,把所有可找到的对象都复制到A空间中,一旦当A空间充满,GC就把在A空间中 可找到的对象都复制到B空间中(会覆盖原有的存储对象),当B空间满的时间,GC就把在B空间中可找到的 对象都复制到A空间中,AB在这个过程中互换角色,那位客官说了:拷来拷去,烦不烦啊?什么时候是头? 您别急,在活动对象经过一定次数的GC操作后,这些活动对象就会被放到旧域中。对于这些活动对象, 新域的幼儿园生活结束了。 新域为什么要这么折腾?

Java 精髓

从宏观上看,编程技术的发展在一定程度上映射了历史的发展。正如人类社会起源于极其简单的原始社会,早期的编程技术具有同样的规律;正如伟大的文明要经历萌芽、繁荣和衰落的过程,编程语言也同样会经历这些过程。国家的兴亡交替促进了人类的进步。同样,编程技术也处于不断的新旧更替之中:新的编程语言取代原来的编程语言。纵观人类历史,总不乏一些关键性事件,例如罗马帝国的颠覆、1066年的不列颠入侵,还有第一次核爆炸,它们都彻底改变了原来的世界。对于编程语言来说,虽然变革的规模较小,但同样改变了编程技术的发展进程。例如,FORTRAN语言的发明彻底改变了计算机编程的方式。Java的发明,则是另一个重大事件。 Java是标志编程进入Internet时代的里程碑。Java的设计初衷就是用来创建可以在Internet 上随处运行的应用程序,其“一次编写,随处运行”的理念定义了一种新的编程规范。Gosling 等人最初将其视为小型问题的解决方案,但后来却成为下一代程序员规划编程前景的动力。Java从根本上改变了人们对于编程的认识,因此计算机语言的发展历史可划分为两个时代:Java前时代和Java后时代。 Java前时代的程序员编写在单机上运行的程序;而Java后时代的程序员则为分布式网络环境编写程序。程序员不再只考虑单机的需求;相反,如今“网络就是计算机”的理念非常流行,程序员应该以服务器、客户端和主机的概念进行思考。 虽然Java的发展是由Internet所驱动的,但Java决不只是一种“Internet语言”。相反,它是一种特征(featuree)完备、为现代网络世界设计的通用编程语言。这意味着Java适合于几乎所有类型的编程。尽管有时候Java受限于网络性能,但它仍然包容了许多推进编程技术发展的新特征。这些特征不断影响着如今的计算模式。例如,Java提出的许多原理后来被C#所模仿。 本书将通过各种类型的应用程序来全面阐述Java语言的功能。其中一些应用程序展示了Java语言独立于网络属性(attribute)的强大功能,即所谓“纯代码”的范例,因为它们展示了Java语法的表示方法和设计思想。一些应用程序展示了如何使用Java语言及其API类简化复杂的网络编程。所有这些应用程序都说明了Java语言的强大功能和广泛应用范围。 在开始本书的Java教程之前,本章会用一些篇幅指出Java的一些特征,它们是Java作为一种优秀编程语言的标志。同时,这些特征也是将本章命名为“Java精髓”的原因所在。 1.1 简单数据类型和对象:完美的平衡 设计一种面向对象语言所面临的最大挑战,就是如何平衡对象和简单数据类型之间的抉择。从纯理论的观点来看,每种数据类型都应该是一个对象,并且都应该从一个共同的父对象派生而来。这就使得所有数据类型以相同的基本模式运作,共享一个公共的基类属性集合。现在的问题在于,如果将简单数据类型(如int和double)作为对象处理,那么对象机制所引起的额外开销会导致性能(performace)的下降。由于简单数据类型通常用于循环控制和条件语句,所以这些额外开销将带来广泛的负面影响。诀窍就是如何在“一切都是对象”的理想和“性能衡量”的现实之间找到正确的平衡点。 Java非常巧妙地解决了对象与简单数据类型之间的问题。首先,Java定义了8种简单类型:byte、short、int、long、char、float、double和boolean。这些类型能够直接转换为二进制代码。因此,CPU可以直接处理int类型的变量,而无需任何额外开销。在Java中,处理简单数据类型和其他语言一样快速高效。因此,由int类型变量所控制的for循环可以高速运行,而不受任何对象化所带来的负面影响。 除了这些简单数据类型,Java中的其他数据类型都从一个共同超类(Object类)派生而来。因此,所有这些数据类型都共享从父类继承而来的方法和属性集。例如,所有对象都有toString()方法,因为toString()是父类Object中定义的方法。 由于简单数据类型不是对象,因此Java可自由地以略有不同的方式处理对象和非对象。这

Java新手必学的21个技术点

Java新手必学的21个技术点 以下为大家盘点作为Java新手必学的21个技术点,希望能够对想要学习编程,学习JAVA的人有些帮助! JNI Java Native Interface,可以允许Java中调用本地接口方法,一般用于C/C++代码的调用。需要注意的是在java中加载so/dll文件的路径问题,本身调用接口并不复杂,但是经常在是否加载了所需的本地接口库中花费较多时间 RMI RemoteMethodInvocation ,java语言特有的远程调用接口,使用还是比较简单方便。不过需要跨语言的情况下,就需要使用 webservice 等其他方式来支持。一般来说,程序都不需要使用RMI,不过可以在特定的情况下使用,我就在一个项目中,使用RMI来进行程序远程启动停止的控制。 标注 也是jdk5 之后引入的。Spring是个优秀的框架,最开始就以xml作为标准的配置文件。不过到了Spring3 之后,尤其是 spring-boot 兴起之后,越来越推崇使用标注来简化xml配置文件了,对于开发者来说,可以节省不少xml配置的时间。但是劣势是在于标注散落在各个类中,不像xml,可以对所有配置有个全局性的理解和管理,所以还没有办法说完全就取代所有的xml。对于一般开发者,会使用标注即可,一些公共组建的开发者可能会需要了解标注的定义和实现,可以在具体需要的时候再细看。 泛型

这是JDK5开始引入的新概念,其实是个语法糖,在编写java代码时会有些许便利,一般的应用或者是业务的开发,只需要简单使用,不一定会用到定义泛型这样的操作,但是开发一些基础公共组件会使用到,可以在需要的时候再细看这个部分,一般情况下只要会简单使用即可。 Maven的使用 Maven 也不是Java里面的内容,但是maven是革命性的,给java开发带来了巨大的便利。从依赖的引入和管理,开发流程的更新和发布产出,乃至版本的更新,使用maven可以大大简化开发过程中的复杂度,从而节省大量时间。可以说,maven已经成为java开发者的标配了。所以我把maven也作为一个java开发者对于基础必备的知识点。以后会再放上一些我的一些对于maven使用的经验和技巧等,这里就不再细说了 XML解析/ JSON解析 其实这两块内容都不是J2SE里面的内容,但是在日常开发中,和其他程序交互,和配置文件交互,越来越离不开这两种格式的解析。 不过对于一个开发者来说,能够了解一些XML/JSON具体解析的原理和方法,有助于你在各个具体的场景中更好的选择合适你的方式来使得你的程序更有效率和更加健壮。 XML:需要了解 DOM解析和 SAX解析的基本原理和各自的适用场景JSON:需要了解一些常用JSON框架的用法, 如 Jackson, FastJson, Gson 等。 时间日期处理

JVM调优总结

JVM调优总结 作者: 和你在一起https://www.360docs.net/doc/6212325061.html, 程序员其实很痛苦的,每隔一段时间就会听到、看到很多很多新名词、新技术---囧.幸而有了互联网,有了开源、有了wiki、有了分享:)—人人为我,我为人人。拓荒者走过的时候很痛苦,但是如果能给后来人留下点路标,是不是可以让他们少走一些弯路呢?踏着前辈的足迹我走到了这里,也应该为后来的人留下点东西。走夜路其实不可怕,可怕的是一个人走夜路:)https://www.360docs.net/doc/6212325061.html, - 做最棒的软件开发交流社区 A-PDF Number Pro DEMO: Purchase from https://www.360docs.net/doc/6212325061.html, to remove the watermark

目 录 1. java路上 1.1 JVM调优总结-序 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .3 1.2 JVM调优总结(一)-- 一些概念 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .4 1.3 JVM调优总结(二)-一些概念 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7 1.4 JVM调优总结(三)-基本垃圾回收算法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9 1.5 JVM调优总结(四)-垃圾回收面临的问题 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .12 1.6 JVM调优总结(五)-分代垃圾回收详述1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .14 1.7 JVM调优总结(六)-分代垃圾回收详述2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .18 1.8 JVM调优总结(七)-典型配置举例1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .26 1.9 JVM调优总结(八)-典型配置举例2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31 1.10 JVM调优总结(九)-新一代的垃圾回收算法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .34 1.11 JVM调优总结(十)-调优方法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .38 1.12 JVM调优总结(十一)-反思 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .47 1.13 JVM调优总结(十二)-参考资料 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .50

JVM详解

JVM详解 本文详细讲解了JVM(Java Virtual Machine)的方方面面,首先由java的特性来描绘JVM 的大致应用,再细细阐述了JVM的原理及内存管理机制和调优.最后讲述了与JVM密切相关的Java GC机制. 本文内容大多来自网络,但内容十分丰富,是学习JVM的好资料. 后面会再针对JVM的两大职责class loader和execution engine进行讲解 若有疑问 目录 Java相关 (2) 1.1Java定义 (2) 1.2Java的开发流程 (2) 1.3Java运行的原理 (3) 1.4半编译半解释 (4) 1.5平台无关性 (5) JVM内存模型 (5) 2.1JVM规范 (6) 2.2 Sun JVM (9) 2.3 SUN JVM内存管理(优化) (10) 2.4 SUN JVM调优 (13) 2.5.JVM简单理解 (16) 2.5.1Java栈 (16) 2.5.2堆 (16) 2.5.3堆栈分离的好处 (19) 2.5.4 堆(heap)和栈(stack) (19) JAVA垃圾收集器 (20) 3.1垃圾收集简史 (20) 3.2常见的垃圾收集策略 (20) 3.2.1Reference Counting(引用计数) (20) 3.2.2跟踪收集器 (21) 3.3JVM的垃圾收集策略 (25) 3.3.1Serial Collector (25) 3.3.2 Parallel Collector (25) 3.3.3 Concurrent Collector (26) Java虚拟机(JVM)参数配置说明 (26)

相关文档
最新文档