jvm学习笔记

来源:互联网 发布:汉口北淘宝商学院 编辑:程序博客网 时间:2024/05/29 03:32
1.局部变量栈上分配,永远不可能出现内存泄露
堆:全局的,方法的。共享线程、
栈:局部的,局部变量,私有线程,一个函数调用结束之后,栈会移除



二:
JVM启动流程
JVM基本结构
内存模型
编译和解释运行的概念
1.java 启动程序
2.装载配置:在当前配置下寻找jvm的配置文件,之后定位jvm.dll
3.根据配置寻找JVM.dll :
4.根据dll,初始化JVM 获得JNIEnv接口:提供了大量的和jvm交互的接口,比如说查找某个类
5.找到main


1.PC寄存器
每个线程拥有一个PC寄存器
在线程创建时 创建
指向下一条指令的地址
执行本地方法时,PC的值为undefined

2.方法区
  保存装载的类信息
       类型的常量池
       字段,方法信息
     方法字节码
 通常和永久区(Perm)关联在一起
JDK6时,String等常量信息置于方法
JDK7时,已经移动到了堆


3.Java堆
     和程序开发密切相关
     应用系统对象都保存在Java堆中
     所有线程共享Java堆
     对分代GC来说,堆也是分代的
     GC的主要工作区间.

复制算法


4.Java栈 :先进后出的结构,
     线程私有
     栈由一系列帧组成(因此Java栈也叫做帧栈) :每次方法调用,都会产生一个帧。并且把这个帧压倒帧栈中
     帧保存一个方法的局部变量、操作数栈、常量池指针
     每一次方法调用创建一个帧,并压栈
Java栈 – 局部变量表 包含参数和局部变量
Java栈 – 函数调用组成帧栈 (递归)
Java栈 – 操作数栈
Java没有寄存器,所有参数传递使用操作数栈
public static int add(int a,int b){
int c=0;
c=a+b;
return c;
}
0:a,1:b,2:c
 0:   iconst_0 // 0压栈 , a
 1:   istore_2 // 弹出int,存放于局部变量2 ,c
 2:   iload_0  // 把局部变量0压栈
 3:   iload_1 // 局部变量1压栈
 4:   iadd      //弹出2个变量,求和,结果压栈
 5:   istore_2 //弹出结果,放于局部变量2
 6:   iload_2  //局部变量2压栈
 7:   ireturn   //返回


Java栈 – 栈上分配
堆上分配 :每次需要清理空间 ,忘记删除的话,就会内存泄露,会发生在任何地方,全局的问题
栈上分配 :函数调用完成自动清理 ,把他当成局部变量先定义,对象就变成局部变量,使用完就会回收

Java栈 – 栈上分配  
     小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
     直接分配在栈上,可以自动回收,减轻GC压力
     大对象或者逃逸对象无法栈上分配 :如果说这个对象,分配出来之后除了在这里要用,其他地方也要用就不可以这样用,因为栈是线程私有的。

对象本身是在堆中的
栈中放的是对象的引用
栈是指向堆的,但是堆中的内容和方法是要到方法区中去找的,

为了能让递归函数调用的次数更多一些,应该怎么做呢?

内存模型
每一个线程有一个工作内存和主存独立
工作内存存放主存中变量的值的拷贝
       当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作
      每一个操作都是原子的,即执行期间不会被中断
      对于普通变量,一个线程中更新的值,不能马上反应在其他变量中
      如果需要在其他线程中立即可见,需要使用 volatile 关键字 


      没有volatile -server 运行 无法停止
      volatile 不能代替锁  一般认为volatile 比锁性能好(不绝对) 
      选择使用volatile的条件是: 语义是否满足应用 ,
volatile :一个线程会不停的监听stop,监听看到改变之后,会有相应的行为。
         是线程不安全的


可见性 :一个线程修改了变量,其他线程可以立即知道 
保证可见性的方法 
        volatile
        synchronized (unlock之前,写变量值回主存)
        final(一旦初始化完成,其他线程就可见) :被定义常量的

有序性
       在本线程内,操作都是有序的
       在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)
指令重排
      线程内串行语义
             写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
             写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
             读后写 a = b;b = 1; 读一个变量之后,再写这个变量。
             以上语句不可重排
             编译器不考虑多线程间的语义
             可重排: a=1;b=2;
指令重排 – 破坏线程间的有序性
class OrderExample {
int a = 0;
boolean flag = false;
public void writer() {
    a = 1;                  
    flag = true;          
}
public void reader() {
    if (flag) {                
        int i =  a +1;      
        ……
    }
}
}
线程A首先执行writer()方法
线程B线程接着执行reader()方法
线程B在int i=a+1 是不一定能看到a已经被赋值为1
因为在writer中,两句话顺序可能打乱 
线程A
flag=true  a=1 
线程B
flag=true(此时a=0


指令重排 – 保证有序性的方法
同步后,即使做了writer重排,因为互斥的缘故,reader 线程看writer线程也是顺序执行的。
class OrderExample {
int a = 0;
boolean flag = false;
public synchronized void writer() {
    a = 1;                  
    flag = true;          
}
public synchronized void reader() {
    if (flag) {                
        int i =  a +1;      
        ……
    }
}
}
线程A
flag=true   a=1 
线程B
flag=true(此时a=1)

指令重排的基本原则
      程序顺序原则:一个线程内保证语义的串行性
      volatile规则:volatile变量的写,先发生于读
      锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
      传递性:A先于B,B先于C 那么A必然先于C
      线程的start方法先于它的每一个动作
      线程的所有操作先于线程的终结(Thread.join())
      线程的中断(interrupt())先于被中断线程的代码
      对象的构造函数执行结束先于finalize()方法

解释运行
      解释执行以解释方式运行字节码
      解释执行的意思是:读一句执行一句
编译运行(JIT)
      将字节码编译成机器码
      直接执行机器码
      运行时编译
      编译后性能有数量级的提升

三:
Trace跟踪参数
堆的分配参数
栈的分配参数

1.Trace跟踪参数
-verbose:gc
-XX:+printGC
可以打印GC的简要信息
堆在GC之前用了4M。GC之后用了374K,回收了将近4M的空间,整个堆的大小是16M左右
[GC 4790K->374K(15872K), 0.0001606 secs]

-XX:+PrintGCDetails
打印GC详细信息
-XX:+PrintGCTimeStamps
打印CG发生的时间戳
GC之前是4M,GC之后被清空了,新生代的总大小是5M
[GC[DefNew: 4416K->0K(4928K), 0.0001897 secs] 4790K->374K(15872K), 0.0002232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

程序结束之后,打印堆的基本状况:
-XX:+PrintGCDetails的输出
Heap
为什么使用内存,总是少于总内存
 def new generation   total 13824K, used 11223K [0x27e80000, 0x28d80000, 0x28d80000)
  eden space 12288K,  91% used [0x27e80000, 0x28975f20, 0x28a80000)
  from space 1536K,   0% used [0x28a80000, 0x28a80000, 0x28c00000)
  to   space 1536K,   0% used [0x28c00000, 0x28c00000, 0x28d80000)
 tenured generation   total 5120K, used 0K [0x28d80000, 0x29280000, 0x34680000)
   the space 5120K,   0% used [0x28d80000, 0x28d80000, 0x28d80200, 0x29280000)
 compacting perm gen  total 12288K, used 142K [0x34680000, 0x35280000, 0x38680000)
   the space 12288K,   1% used [0x34680000, 0x346a3a90, 0x346a3c00, 0x35280000)
    ro space 10240K,  44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)
    rw space 12288K,  52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)
共享区间,(ro rw

-Xloggc:log/gc.log
指定GC log的位置,以文件输出
帮助开发人员分析问题

-XX:+PrintHeapAtGC
每次一次GC后,都打印堆信息

-XX:+TraceClassLoading
监控类的加载
[Loaded java.lang.Object from shared objects file]
[Loaded java.io.Serializable from shared objects file]
[Loaded java.lang.Comparable from shared objects file]
[Loaded java.lang.CharSequence from shared objects file]
[Loaded java.lang.String from shared objects file]
[Loaded java.lang.reflect.GenericDeclaration from shared objects file]
[Loaded java.lang.reflect.Type from shared objects file]

-XX:+PrintClassHistogram
按下Ctrl+Break后,打印类的信息:
分别显示:序号、实例数量、总大小、类型 

2.堆的分配参数
-Xmx –Xms
指定最大堆和最小堆
-Xmx20m -Xms5m  运行代码:
System.out.print("Xmx=");
System.out.println(Runtime.getRuntime().maxMemory()/1024.0/1024+"M");
System.out.print("free mem=");
System.out.println(Runtime.getRuntime().freeMemory()/1024.0/1024+"M");
System.out.print("total mem=");
System.out.println(Runtime.getRuntime().totalMemory()/1024.0/1024+"M");

-Xmx 和 –Xms 应该保持一个什么关系,可以让系统的性能尽可能的好呢?
如果你要做一个Java的桌面产品,需要绑定JRE,但是JRE又很大,你如何做一下JRE的瘦身呢?


-Xmn
设置新生代大小
-XX:NewRatio
       新生代(eden+2*s)和老年代(不包含永久区)的比值
       4 表示 新生代:老年代=1:4,即年轻代占堆的1/5
-XX:SurvivorRatio
      设置两个Survivor区和eden的比
      8表示 两个Survivor :eden=2:8,即一个Survivor占年轻代的1/10

最大20M,最小20M,Xmn:新生代1M(eden+2*s

-Xmx20m -Xms20m -Xmn1m  -XX:+PrintGCDetails

-XX:+HeapDumpOnOutOfMemoryError
OOM时导出堆到文件
-XX:+HeapDumpPath
导出OOM的路径
-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump
 Vector v=new Vector();
        for(int i=0;i<25;i++)
            v.add(new byte[1*1024*1024]);
-XX:OnOutOfMemoryError
在OOM时,执行一个脚本
"-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p“        可以执行一个bat文件,%p当前java进程的pid
                                                把dump文件放到了了a.txt中
 当程序OOM时,在D:/a.txt中将会生成线程的dump
可以在OOM时,发送邮件,甚至是重启程序


根据实际事情调整新生代和幸存代的大小
官方推荐新生代占堆的3/8  
幸存代占新生代的1/10  
在OOM时,记得Dump出堆,确保可以排查现场问题


-XX:PermSize  -XX:MaxPermSize 
   设置永久区的初始空间和最大空间
   他们表示,一个系统可以容纳多少个类型 :一般不是太大,一个系统中几百兆几十兆就够了


3.永久区分配参数
使用CGLIB等库的时候,可能会产生大量的类,这些类,有可能撑爆永久区导致OOM
for(int i=0;i<100000;i++){
    CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
}
打开堆的Dump
堆空间实际占用非常少
但是永久区溢出 一样抛出OOM

4.栈大小分配:线程在私有的一块区域,里面就是栈帧:局部变量表,常量池的引用,返回地址
-Xss
      通常只有几百K :想多跑些线程的话,这个数就要减小
      决定了函数调用的深度
      每个线程都有独立的栈空间
      局部变量、参数 分配在栈上


四:
引用计数法的问题
引用和去引用伴随加法和减法,影响性能
很难处理循环引用

1.标记清除法:标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。

2.标记-压缩算法:适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。

3.复制算法:
与标记-清除算法相比,复制算法是一种相对高效的回收方法
不适用于存活对象较多的场合 如老年代
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
复制算法的最大问题是:空间浪费 整合标记清理思想 (将老年代和大对象新开设一片内存,在清空垃圾对象)
eden区:就是整个的对象区 新生代
from区:是复制算法中的两个完全一样的区 
to区:是复制算法中的两个完全一样的区    

4.分代思想:
依据对象的存活周期进行分类,短命对象归为新生代(都认为是存活率比较低的,复制操作),长命对象归为老年代(大量存活对象,若干次的GC都没有被回收之后,才进入老年代,要么就是新生代回收的时候空间不够,大部分都是生命周期比较长的,和程序一样的周期长适合标记清理或者标记压缩)。
根据不同代的特点,选取合适的收集算法
少量对象存活,适合复制算法
大量对象存活,适合标记清理或者标记压缩

所有的算法,需要能够识别一个垃圾对象,因此需要给出一个可触及性的定义


5.

a. 可触及的 (不可回收)

    从根节点可以触及到这个对象

b. 可复活的

    一旦所有引用被释放,就是可复活状态

    因为在finalize()中可能复活该对象

c.  不可触及的

    在finalize()后,可能会进入不可触及状态

   不可触及的对象不可能复活

   可以回收


经验:避免使用finalize(),操作不慎可能导致错误。
优先级低,何时被调用, 不确定
何时发生GC不确定
可以使用try-catch-finally来替代它

根 :
栈中引用的对象
方法区中静态成员或者常量引用的对象(全局对象)
JNI方法栈中引用对象

Stop-The-World
Java中一种全局暂停的现象
全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互
多半由于GC引起
Dump线程
死锁检查
堆Dump
GC时为什么会有全局停顿?
类比在聚会时打扫房间,聚会时很乱,又有新的垃圾产生,房间永远打扫不干净,只有让大家停止活动了,才能将房间打扫干净。
危害
长时间服务停止,没有响应
遇到HA系统,可能引起主备切换,严重危害生产环境。(长时间程序停止,主机无响应,备机起来工作,使得,主备机都启动,,使数据不一致,系统错乱
新生代的GC 停顿的时间较少,不会造成系统很大的影响  零点几秒就可以解决
老年代的GC 停顿的时间较大,可以达到到几秒甚至一百多秒几十分钟,一般GC堆越大,扫描时间越大

[Times: user=0.42 sys=0.00, real=0.43 secs]:在程序运行到0.42秒时候,停止,GC回收时间是0.43秒


总结:GC算法总结整理

引用计数 : 没有被Java采用 

标记-清除 :标记-压缩 比 标记-清除 有何优势

标记-压缩

复制算法

新生代


五:

edens0s1tenured栈上分配from to大对象对象一般在新生代生成幸存下来的老年代1.GC参数 - 串行收集器
最古老,最稳定,单线程
效率高
可能会产生较长的停顿
-XX:+UseSerialGC
新生代、老年代使用串行回收
新生代复制算法
老年代标记-压缩
GC线程只有,一旦开始,应用程序暂停。当GC结束,应用程序运行
2.GC参数 - 并行收集器
ParNew
-XX:+UseParNewGC
新生代并行
老年代串行
Serial收集器新生代的并行版本,指定线程回收的数量
复制算法
多线程,需要多核支持
-XX:ParallelGCThreads 限制线程数量
0.834: [GC 0.834: [ParNew: 13184K->1600K(14784K), 0.0092203 secs] 13184K->1921K(63936K), 0.0093401 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

3.GC参数 -并行收集器
Parallel收集器
类似ParNew
新生代复制算法
老年代 标记-压缩
更加关注吞吐量
-XX:+UseParallelGC
使用Parallel收集器+ 老年代串行
-XX:+UseParallelOldGC
使用Parallel收集器+ 并行老年代

1.500: [Full GC [PSYoungGen: 2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K)] 30717K->30437K(62848K) [PSPermGen: 10943K->10928K(32768K)], 0.2902791 secs] [Times: user=1.44 sys=0.03, real=0.30 secs]

-XX:MaxGCPauseMills :GC回收的时候,所占用的时间,尽量保证回收时间不超过这个值,希望占用时间最短
最大停顿时间,单位毫秒
GC尽力保证回收时间不超过设定值
-XX:GCTimeRatio :GC回收时间占总时间的比,吞吐量。单位时间里面,CPU是分在应用程序上还是GC上,
0-100的取值范围
垃圾收集时间占总时间的比
默认99,即最大允许1%时间做GC
这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优
垃圾回收的工作总量是一定的,可以安排GC的频率提高,时间就变少。系统的整体性能就不好了
少做几次GC,每次GC回面向很多垃圾,停顿就比较长

两个同时提高的话,优化GC算法

4.GC参数 – CMS收集器:尽量将单独占用的时间减少,但是还是不可避免。在清理完成后,还是会有垃圾对象的,因为和应用程序一起执行的
CMS收集器:老年代的回收期
Concurrent Mark Sweep 并发标记清除:与用户线程一起执行
标记-清除算法
与标记-压缩相比
并发阶段会降低吞吐量
老年代收集器(新生代使用ParNew)
-XX:+UseConcMarkSweepGC

CMS运行过程比较复杂,着重实现了标记的过程,可分为
    a  初始标记(全局停顿)
              根可以直接关联到的对象
              速度快
    b.  并发标记(和用户线程一起,这个是主要的工作)
             主要标记过程,标记全部对象
    c.  重新标记(和用户线程一起,修正)
             由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
   d. 并发清除全局停顿,因为在清理的时候,对象还在运行,我们只要简单的堆其进行清空就可以了,压缩或者复制算法的话,要 重新整理内存空间,移动位置的话,应用程序就很难继续执行了,
                         他是和用户线程一起去工作的,如果清理的话,移动这些可用的对象空间,用户线程就有可能找不到这些对象在哪里,因此很难和用户程序并发去执行
            基于标记结果,直接清理对象
1.662: [GC [1 CMS-initial-mark: 28122K(49152K)] 29959K(63936K), 0.0046877 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  
1.666: [CMS-concurrent-mark-start]
1.699: [CMS-concurrent-mark: 0.033/0.033 secs] [Times: user=0.25 sys=0.00, real=0.03 secs]
1.699: [CMS-concurrent-preclean-start]
1.700: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
1.700: [GC[YG occupancy: 1837 K (14784 K)]1.700: [Rescan (parallel) , 0.0009330 secs]1.701: [weak refs processing, 0.0000180 secs] [1 CMS-remark: 28122K(49152K)] 29959K(63936K), 0.0010248 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
1.702: [CMS-concurrent-sweep-start]
1.739: [CMS-concurrent-sweep: 0.035/0.037 secs] [Times: user=0.11 sys=0.02, real=0.05 secs]
1.739: [CMS-concurrent-reset-start]
1.741: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

特点
  尽可能降低停顿
  会影响系统整体吞吐量和性能
        比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半
  清理不彻底
       因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理
  因为和用户线程一起运行,不能在空间快满时再清理
       -XX:CMSInitiatingOccupancyFraction设置触发GC的阈值
       如果不幸内存预留空间不够,就会引起concurrent mode failure

 使用串行收集器作为后备
33.348: [Full GC 33.348: [CMS33.357: [CMS-concurrent-sweep: 0.035/0.036 secs] [Times: user=0.11 sys=0.03, real=0.03 secs]
 (concurrent mode failure): 47066K->39901K(49152K), 0.3896802 secs] 60771K->39901K(63936K), [CMS Perm : 22529K->22529K(32768K)], 0.3897989 secs] [Times: user=0.39 sys=0.00, real=0.39 secs]
   
有关碎片
标记-清除和标记-压缩
   标记-清除:内存空间有大量碎片产生,还需要
   标记-压缩:有大量连续空间产生,比如说数组就需要连续空间
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次整理
      –整理过程是独占的,会引起停顿时间变长  
-XX:+CMSFullGCsBeforeCompaction
      –设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads
     –设定CMS的线程数量

为减轻GC压力,我们需要注意些什么?

软件如何设计架构

代码如何写

堆空间如何分配


-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:SurvivorRatio:设置eden区大小和survivior区大小的比例
-XX:NewRatio:新生代和老年代的比
-XX:+UseParNewGC: 新生代使用并行收集器
-XX:+UseParallelGC :新生代使用并行回收收集器
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:ParallelGCThreads:设置用于垃圾回收的线程数
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:设定CMS的线程数量
-XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发
-XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
-XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收
-XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收


JDK6:使用32M堆处理请求
参数:
set CATALINA_OPTS=-server -Xloggc:gc.log -XX:+PrintGCDetails -Xmx32M -Xms32M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseSerialGC -XX:PermSize=32M

JDK6:使用最大堆512M堆处理请求
参数:
set CATALINA_OPTS=-Xmx512m -XX:MaxPermSize=32M  -Xloggc:gc.log -XX:+PrintGCDetails
结果:FULL GC很少,基本上是Minor GC 

JDK6:使用最大堆512M堆处理请求
参数:
set CATALINA_OPTS=-Xmx512m -Xms64m -XX:MaxPermSize=32M  -Xloggc:gc.log -XX:+PrintGCDetails
结果 GC数量减少 大部分是Minor GC 

JDK6:使用最大堆512M堆处理请求
参数:
set CATALINA_OPTS=-Xmx512m -Xms64m -XX:MaxPermSize=32M  -Xloggc:gc.log -XX:+PrintGCDetails -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=4
结果:GC压力原本不大,修改GC方式影响很小 

JDK 6
set CATALINA_OPTS=-Xmx40m -Xms40m -XX:MaxPermSize=32M  -Xloggc:gc.log -XX:+PrintGCDetails
减小堆大小,增加GC压力,使用Serial回收器 

JDK 6
set CATALINA_OPTS=-Xmx40m -Xms40m -XX:MaxPermSize=32M  -Xloggc:gc.log -XX:+PrintGCDetails -XX:+UseParallelOldGC -XX:ParallelGCThreads=4
减小堆大小,增加GC压力,使用并行回收器 

JDK 6
set CATALINA_OPTS=-Xmx40m -Xms40m -XX:MaxPermSize=32M  -Xloggc:gc.log -XX:+PrintGCDetails -XX:+UseParNewGC
减小堆大小,增加GC压力,使用ParNew回收器 
只是影响了新生代,老年代才是GC回收的关键,这个有一定帮助,但是帮助不会太大

启动Tomcat 7
使用JDK6
不加任何参数启动测试
升级JDK可能会带来额外的性能提升!
不要忽视JDK的版本哦






堆从GC回收之前用4M,在回收之后,用了374K,就是说回收了4M左右,而整个堆的大小在16M左右,
新生代
老年代
持久代,永久区方法区


这个java虚拟机,只要一启动 ,堆至多至少能够使用的java堆:
基本会在最小内存上面不变的,实在不行才会向上扩容


设置绝对值



设置百分比 新生代的百分比


也是新生代


GC的次数自己调节的越少越好,这样的话,很多的对象就没有机会进到老年代了






栈空间,尽量小,


栈空间的溢出




引用计数法:一个对象被引用就加一标记,当标记为零的时候,这个对象就会被释放


新生代
GC复制算法,还是需要空间进行担保的,对于大对象,直接进入担保空间(老年代),大对象,尽可能不要放到复制空间(所以老年区还是有一部分是新生代的)
eden:对象产生的区域
from:两块空间是一样大的,就是复制算法的两块空间
to:

老年代的对象,经过几次垃圾回收,都没有回收的掉,所以才会进入老年代,甚至和应用程序的生命周期一样长,这个时候比较适合标记清理和标记压缩算法

引用计数,不适合循环引用,所以java不使用
老年代使用,标记清楚和标记压缩
复制压缩,是新生代明确使用的方法




串行收集器:-XX:+UseSerialGC
并行收集器:-XX:+UseParNewGC  -XX:ParallelGCThreads
并行收集器:-XX:+UseParallelGC  -XX:+UseParallelGC
CMS收集器:-XX:+UseConcMarkSweepGC     Concurrent Mark Sweep 并发标记清除


标记清楚和标记压缩:碎片问题,影响下面的再次分配












































































0 0
原创粉丝点击