1 jvm虚拟机中有数据类型分为两种,基本类型和引用类型,基本类型包括byte,short,int,long,float,double,boolean,char;引用类型包括类类型,接口类型,和数组
2堆和栈是程序运行的关键,栈是运行时的单位,解决运行是的问题,堆是存储的单位,解决存储的问题。
3一个线程需要一个独立的线程栈,而所有的线程共享堆中的数据。
1堆和栈的分离,使得堆中的数据可以被多个线程共享,一方面提供了有效的数据交互方式,另一方面,堆中的共享的常量和缓存可以被所有栈访问,节省了空间。
2栈因为运行需要,比如保存系统运行的上下文,需要进行地址划分,只能向上增长,限制栈存储内容的能力。而堆中的内容可以根据需要动态增长。
1在java中main就是栈的起点,也是程序的起始点。
2堆中存放的是对象。栈中存放的是基本数据类型和对象的引用。对象的大小不可估计。而基本数据和对象的引用对应的大小是固定的的,占用的空间小。
3程序运行永远在栈中进行,传递参数是只存在传递基本数据和对象的引用的副本,而不是传递对象本身。在栈中,基本类型和引用的处理是一样的,都是传值。
下面是一个很好的列子,别的地方找的。
- class Test
- {
- public static void main(String args[])
- {
- int val;
- StringBuffer sb1, sb2;
- val = 10;
- sb1 = new StringBuffer ("apples" );
- sb2 = new StringBuffer ("pears" );
- System .out.println("val is " + val);
- System .out.println("sb1 is " + sb1);
- System .out.println("sb2 is " + sb2);
- System .out.println("" );
- System .out.println("calling modify" );
- // 按值传递所有参数
- modify(val, sb1, sb2);
- System .out.println("returned from modify" );
- System .out.println("" );
- System .out.println("val is " + val);
- System .out.println("sb1 is " + sb1);
- System .out.println("sb2 is " + sb2);
- }
- public static void modify(int a, StringBuffer r1,
- StringBuffer r2)
- {
- System .out.println("in modify..." );
- a = 0;
- r1 = null ; //1
- r2.append(" taste good" );
- System .out.println("a is " + a);
- System .out.println("r1 is " + r1);
- System .out.println("r2 is " + r2);
- }
- }
Java 应用程序的输出
- val is 10
- sb1 is apples
- sb2 is pears
- calling modify
- in modify...
- a is 0
- r1 is null
- r2 is pears taste good
- returned from modify
- val is 10
- sb1 is apples
- sb2 is pears taste good
这段代码声明了三个变量:一个整型变量和两个对象引用。设置了每个变量的初始值并将它们打印出来。然后将所有三个变量作为参数传递给 modify 方法。
modify 方法更改了所有三个参数的值:
将第一个参数(整数)设置为 0 。
将第一个对象引用 r1 设置为 null 。
保留第二个引用 r2 的值,但通过调用 append 方法更改它所引用的对象(这与前面的 C++ 示例中对指针 p 的处理类似)。
当执行返回到 main 时,再次打印出这三个参数的值。正如预期的那样,整型的 val 没有改变。对象引用 sb1 也没有改变。如果 sb1 是按引用传递的,正如许多人声称的那样,它将为 null 。但是,因为 Java 编程语言按值传递所有参数,所以是将 sb1 的引用的一个副本传递给了 modify 方法。当 modify 方法在 //1 位置将 r1 设置为 null 时,它只是对 sb1 的引用的一个副本进行了该操作,而不是像 C++ 中那样对原始值进行操作。
另外请注意,第二个对象引用 sb2 打印出的是在 modify 方法中设置的新字符串。即使 modify 中的变量 r2 只是引用 sb2 的一个副本,但它们指向同一个对象。因此,对复制的引用所调用的方法更改的是同一个对象。
java对象的大小
一个空Object的大小为8字节。
引用类型
强引用,软引用,弱引用
强引用 我们在生成对象是Java虚拟机生成的引用,强引用环境下不会被垃圾回收,而软引用和弱引用一般做为缓存来使用。弱引用一定会被垃圾回收。软引用在内存紧张的时候会被垃圾回收。我们一般都是用的强引用。
基本垃圾回收算法
引用技术:此对象有一个引用,增加一个计数,减少一个就计数减一,垃圾回收时,收集计数为0的对象。致命的无法处理循环引用问题。
标记-清除:从引用节点开始标记所有被引用的对象,遍历整个堆,清除没有引用的对象。
复制:把内存划分为两个部分,垃圾回收时,遍历使用的部分,把正在使用的对象复制到另一个区域,清空原来的区域。算法缺点,要使用两倍的内存空间。
标记-复制:结合复制和清楚的优点,标记正在使用的对象,把正在使用的对象压缩到堆的其中一块。
按分区对待的方式划分
增量收集:在应用进行的时候同时回收垃圾。
对象建立和对象回收问题
1 垃圾回收前,暂停对象建立。清理完成之后再运行,应用太大的时候暂停时间太长。
2 并发垃圾回收:回收和建立同时进行,对性能要求提高,而且碎片不好整理。
分代收集:把对象分为年轻代,年老代,持久代,不同生命周期采用不同的垃圾回收算法。现在的垃圾回收器都是按照此算法。
把不同的对象放在不同的代上,分别对待,采取不同的回收算法,提高效率。
新生成的对象都放在年轻代,年轻代分为三个区,存活时间很久的会进入老年代,老年代中存活的都是生命周期很长的,持久代中存放的静态文件如java类,方法。
java回收的起点是一些根对象(java栈,静态变量,寄存器。。)对 应 标 记-清除。从起点遍历,找到正在使用的对象,没有使用的就回收。
复制,和标记-整理方式都可以清理碎片。
垃圾回收分两种类型 一种scavenge gc主要处理年轻代,把第一个去中没存活的对象回收,在把存活的放在第二个区,
还有一种对整个堆进行清理,在年老代被写满,持久代写满用到full gc,full gc很慢,少用。
按照系统线程分
串行收集:单处理器机器,也可以用在小数据量(100m)情况的多处理器机器。可以使用-XX:+UseSerialGC打开。
并行收集:使用多线程垃圾回收工作,速度快,效率高。对老年代还是默认单线程,可以使用-XX+UserParallelGC打开,使用-XX:+UseParallelOldGC打开老年代多线程。使用-XX:ParallelGCThreads=<n>设置并行的线程数,N可以和机器处理器数量相等。最大垃圾回收暂停,-XX:MaxGCPauseMillis=<N>,N为毫秒,指定这个值会减少应用的吞吐量。 吞吐量是垃圾回收时间和非垃圾回收时间的对比,通过-XX:GCTimeRatio=<N>设定,公式为1/(1+n),如n=19,则为5%,默认为1%。适合多CPU,对应用响应时间无要求的中,大型应用。
并发收集:前面两个在垃圾回收是会暂停整个运行环境,适合对响应要求比较高的中,大规模应用。使用-XX:UseConcMarkSweepGC打开。对处理器要求很高。
以下配置主要针对分代垃圾回收算法
JVM中堆的大小设置有三个限制,相关操作系统的数据模型(32位64位),系统可用虚拟内存限制,机器物理内存限制,32位系统一般1.5到2G
典型设置
-Xmx3550m:设置JVM最大可用内存3550M,
-Xms3550m:JVM初始内存为3550m,可以与和第一个相同,避免垃圾回收完成之后JVM重新分配内存。
-Xmn2g:设置年轻代大小2G;整个堆大小=年轻代+年老代+持久代大小,持久代一般固定为64m,增大年轻代,将影响年老代的大小,对系统性能影响很大,Sun公司推荐为整个堆的3/8;
-Xss128K;设置每个线程的栈的大小,减少这个值能生成更多的线程,但操作系统对线程的个数有限制的,一般为3000-5000;
并行收集器适合对吞吐量优先的系统,
并行收集器适合对响应时间优先的系统,并发收集器不会对内存空间进行压缩整理。
辅助打印信息,以供调试
-XX:+PrintGC;-XX:+PrintGCDetails;-XX:+PrintGCTimeStamps可以和前两个混合使用。
常见配置
堆配置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=N:设置年轻代大小
-XX:NewRatio=N设置年轻代和老年代的比例 比如n=3;表示年轻代和老年代 1:3
-XX:SurvivorRatio=n 年轻代中Eden区和Survivor区的比值,Survivor有两个区,n=3,表示一个Survivor区和Eden区比是1:3,整两个Survivor区和Eden区为2:3
-XX:MaxPermSize=n 设置持久代的大小
收集器设置
-XX:+UseSerialGC 设置串行收集器
-XX:+UseParallelGC 设置并行收集器
-XX:+UseParallelOldGC 设置老年代并行收集器
-XX:+UseConcMarkSweepGC 设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThread=n设置并行收集器收集时使用的CPU数,并行收集线程数
-XX:MaxGCPauseMillis=n设置并行收集的最大暂停时间
-XX:GCTimeRatio=n设置垃圾回收占程序运行时间的百分比 公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式,使用于单CPU
年轻代大小的设置
响应时间和吞吐量优先的都应该尽可能的设置大一点
年老代大小的设置
吞吐量优先的一般都是一个很大的年轻代和一个较小的年老代,这样可以尽可能的回收短期对象,减少中期对象,年老对象都是存放的生命周期长的对象。
响应时间优先的要考虑很多因素,要看情况而定
年老代可能会出现碎片,需要如下配置
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩
-XX:CMSFullGCsBeforeCompaction=0:上面设置开启的情况下,设置多少Full Gc后,对年老代进行压缩。
JVM调优工具
Jconsloe JDK自带,性能要求低
JProfiler:功能强大,需付费
VisualVM:JDK自带,功能强大,推荐。
一般都有这些功能
1 堆信息查看(堆空间的大小分配(年轻代,年老代,持久代))
可以解决如下问题 查看 年老代,年轻代大小划分是否合理 ,内存泄漏,垃圾回收算法是否合理
2 提供即时垃圾回收功能
3 垃圾监控(长时间监控回收情况)
4 查看堆内存,对象信息查看:数量,类型。
5 对象引用情况查看
6 线程监控,线程数量,各个线程都处在什么情况下。
7 热点分析 CPU热点:检查系统哪些方法占用的大量CPU时间。 内存热点:检查哪些对象在系统中数量最大,可以根据找到的热点,有针对性的对系统进行优化
8 内存泄漏检查(年老代堆空间被沾满,持久代被占满)。