天天看点

jvm学习之jvm基础

java虚拟机是一台执行java字节码的虚拟计算机,他独立的运行机制,无论什么语言,只要最终编译成jvm识别的字节码,那么他就可以在jvm上运行。立足于jvm可以实现各种各样的跨平台。以前断断续续学过jvm,后来发现长时间不看还是容易忘的,还是整理一下比较好。jvm内容比较多,涉及底层汇编也比较枯燥,但是学习他对于我们理解一些关键问题是很有帮助的,今天先从基础的也比较重要的内存分配开始。

一.内存模型简述

先看几个图,这几个是我照着《实战java虚拟机》中画的,感觉这几个图不错,另外再给大家推荐个免费画图的在线网站:www.processon.com;画UML神马的特别方便 。

jvm学习之jvm基础

了解jvm的童鞋对这几个图应该不陌生,我这里在明确一下:

类加载子系统:负责从文件系统或则网络中加载Class信息

方法区:存放加载后的类信息,运行时常量池信息(字符串常量,数字常量)

java堆:java堆在虚拟机启动的时候建立,几乎所有的java对象实例都存放其中,堆空间是所有线程共享的。

直接内存:其在java堆外,直接向系统申请的内存空间,访问速度优于java堆。

垃圾回收系统:可以对方法区,java堆和直接内存进行回收,其中,java堆是垃圾回收的重点。

java栈:每一个虚拟机线程都有一个私有的栈,一个线程的java栈在线程创建的时候创建,java栈保存帧信息。

本地方法栈:与java栈类似,但是java栈用于java方法的调用,而本地方法用于本地方法的调用。(C编写)

PC:每个线程私有的空间,指向当前正在被执行的指令。

执行引擎:执行虚拟机字节码。

这里面最重要的莫过于堆和java栈了,java的内存的分配和调用和这两个有着密切的联系。

jvm学习之jvm基础

对于堆,如上面的图画的,分为新生代和老年代,新生代又分为几个部分,这个我觉得可能是叫法不一样,有些书把s0,s1也写成from to。对象首先分配在eden区,在一次新生代回收时,如果对象还存活,就进入s0,或s1区,每一次新生代回收,对象加入过存活,年龄就加一,达到一定条件,就会认为是老年对象,进入老年代。

java栈

jvm学习之jvm基础

java栈是和线程密切相关的,线程的基本行为是函数调用,java栈主要内容为栈帧,每一次函数调用都会有一个对应的栈帧压人java栈,调用结束,跳出栈。一个栈帧中,包含局部变量表,操作数栈,帧数据区。

局部变量表只在当前调用的函数有效,栈帧销毁,局部变量表也销毁,需要注意的是,栈帧中的局部变量表的槽位是可以复用的,如果一个局部变量过了其作用域,那么在其后申明的局部变量就会占用他的槽位, 要记住只要被局部变量表直接或间接引用的对象是不会被回收的。这个对于垃圾回收的理解是很重要的,下面我举个例子,看代码

public class VarGc {

public void varGc1(){
    byte[] a = new byte[5*1024*1024];
    System.gc();
}
public void varGc2(){
    byte[] a = new byte[5*1024*1024];
    a = null;
    System.gc();
}
public void varGc3(){
    {
        byte[] a = new byte[5*1024*1024];
    }
    System.gc();
}
public void varGc4(){
    {
        byte[] a = new byte[5*1024*1024];
    }
    int c = 5;
    System.gc();
}
public void varGc5(){
    varGc1();
    System.gc();
}
public static void main(String[] args) {
    VarGc varGc = new VarGc();
    varGc.varGc4();

}
           

}

这里采用虚拟机参数:-XX:+PrintGC
测试4的结果:
[GC 6308K->656K(54272K), 0.0029735 secs]
           

上面这段程序很好理解,下面我们分析下他的垃圾回收过程:

对于函数1:数组被a引用,无法进行回收

函数2:数组失去强引用,可以回收

函数3:虽然a离开了作用域,但是a还存在于局部变量表中,不能回收

函数4:变量c复用了变量a的字变量a销毁,可以回收

函数5:调用函数1后,他的栈帧销毁,包含栈帧所有的局部变量都销毁,可以回收

操作数栈:保存计算过程的中间结果

帧数据区:保存访问常量池的指针

方法区

有的书上也叫永久区,类的字段,方法,常量池都保存在这,如果系统定义太多的类,就会导致方法区溢出。

二.jvm常用参数

1.垃圾回收日志

-XX:PrintGC

:遇到垃圾回收,就会打印日志,比如上面我们的例子

[GC 6308K->656K(54272K), 0.0029735 secs]

GC前堆空间使用量约6M,GC后,堆空间使用量656k,当前可用的堆空间一共约54M

-XX:+PrintGCDetail: 这个指令会打印出详细的GC日志,堆各个区的变化等

2.类加载卸载:

-verbose:class :跟踪类加载或卸载

也可以用:-XX:+TraceClassLoading -XX:+TraceClassUnloading

这个一般查看动态代理生成类的时候就可以用

3.配置堆:

-Xms:指定初始堆的大小

-Xmx:最大堆的大小

可以知道,当前总内存不小于-Xms,当前总内存总是在-Xms和-Xmx之间,从-Xms开始根据需要增长

-Xmn:设置新生代的大小

-XX:SurvivorRatio:设置eden空间与from/to空间的比例关系

-XX:NewRatio:设置老年代与新生代的比例

4.方法区配置:

-XX:PermSize:初始方法区大小

-XX:MaxPermSize:最大方法区大小

5.栈配置

-Xss:指定栈的大小

继续阅读