[Java]JVM简介

写在前面

在虚拟机中,内存被分为不同的区域,大致包括:
  • 方法区
  • 运行时常量池
  • 本机直接内存
  • 程序计数器
在c/c++编程的时候我们需要把自己申请的内存回收掉,不然会导致内存泄露。但是在java中是不需要的,因为有gc线程会帮我们将不需要的对象回收掉,但这样经常会出现问题。

我们最开始知道的Java的字节码是解释执行的,然后会对热的代码编译成本地机器码执行。这样做有两个好处:
  • 解释执行使得启动的速度非常快
  • 选择性地编译成本地代码又使执行的效率没有太差
这样的处理方式使得java能适应跟多的应用场景。

数据

在JVM中有很多种数据,下面依次来看。

对象

在程序中用到最多的就是对象,通常通过引用来访问对象,而对象的大小也有一定的规则。通常情况下GC看到堆被分成下面几部分:
  1. E
  2. S0
  3. S1
  4. O
  5. Perm
对象基本上都是在堆上分配的,相关的参数有:
  • -Xms:初始堆大小
  • -Xmx:堆的最大值
  • -Xmn:年轻代的大小
分配对象时,现在TLAB(Thread Local Allocation Buffer)中分配:
  • -XX:TLABWasteTargetPercent:TLAB挪借的E的空间的百分比(默认1%)
  • -XX:+UseTLAB
因为每个线程都有自己的空间,所以此时分配空间是不需要加锁的。但是TLAB没有空间的时候就需要在E中分配了,因为各个线程都可能需要在E中分配,那么这个时候就需要加锁了:
  • -XX:SurvivorRatio:E、S的比例,默认是8
  • -XX:NewRatio:E和O的比例
有时候我们认为大的对象一般活的比较久(如果不是这样的话是不是代码有问题?),那么新对象可能直接进O区了:
  • -XX:PretenureSizeThreshold:直接在O中分配的阀值
下面接着来看类信息。

类信息

首先来看类信息包含的内容:
  • 类的结构信息
  • 字段描述
  • 方法描述
  • 常量表
这些东西都放在方法区,需要注意的是在运行期也可以往方法区塞东西,比较常见的有:
  • ClassLoader不停地加载类
  • String.intern()
可以在jvm启动的时候来设置方法区的大小:
  • -XX:PermSize
  • -XX:MaxPermSize
这部分数据相关的GC参数有:
  • -XX:+CMSClassUnloadingEnabled
  • -XX:+UseCMSInitiatingOccupancyOnly
  • -XX:CMSInitiatingPermOccupancyFraction=80

变量

简单类型变量的生命周期随着方法的返回而结束,那么对应的内存也会被释放掉,所以这部分不会涉及到GC,栈的大小可以设置:
  • -Xss:每个线程的堆栈的大小
在运行期间jvm对栈以栈帧为单位进行压栈、出栈操作,栈帧中的内容包括:
  • 局部变量区
  • 操作数
  • 帧数据

垃圾回收

和GC相关的参数很多,首先如果想观察GC的信息可以设置参数:
  • -verbose:gc
  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationStoppedTime
  • -XX:+PrintGCApplicationConcurrentTime
  • -XX:+PrintHeapAtGC
  • -XX:+PrintClassHistogram
  • -XX:+PrintTLAB
在发生OOM的时候我们需要查看到底发生了什么,那么可以将堆dump到文件中:
  • -XX:+HeapDumpOnOutOfMemoryError
  • -XX:HeapDumpPath=~/heap.hprof
另外对于回收器在执行期间的行为也可以进行控制:
  • -XX:ParallelGCThreads:并行GC时进行内存回收的线程数
  • -XX:GCTimeRatio:GC时间占总时间的比率
  • -XX:MaxGCPauseMills:GC最大的停顿时间
  • -XX:CMSInitiatingOccupancyFraction:出发GC的老年代使用率,默认为68%
  • -XX:UseCMSCompactAtFullCollection:在CMS完成后进行碎片整理
  • -XX:CMSFullGCsBeforeCompaction:CMS多少次之后进行一次碎片整理
  • -XX:HandlePromotionFailure:设置是否允许分配担保失败
  • -XX:ParallelGCThreads:并行GC时进行内存回收的线程数
我们要查看GC日志的时候基本是先从日志里面看,YoungGC的日志如下:
[ParNew: 2327071K->122000K(2403008K), 0.0942260 secs] 3080564K->891474K(3975872K), 0.0944310 secs] [Times: user=0.39 sys=0.00, real=0.10 secs]
他们的含义依次为:
  1. gc前新生代占用的内存
  2. gc后新生代占用的内存
  3. 新生代大小
  4. jvm暂停的时间
  5. jvm堆gc前的大小
  6. jvm堆gc后的大小
  7. jvm堆大小
  8. jvm消耗的时间
然后在看CMS的GC日志,首先是初始标记:
[GC [1 CMS-initial-mark: 1259285K(1572864K)] 1406035K(3975872K), 0.0697870 secs] [Times: user=0.07 sys=0.00, real=0.08 secs]
并发标记:
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.871/0.871 secs] [Times: user=1.76 sys=0.03, real=0.87 secs]
预先清理:
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.012/0.015 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[CMS-concurrent-abortable-preclean-start]
并发清理:
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.933/0.933 secs] [Times: user=0.93 sys=0.01, real=0.93 secs]
重置线程:
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.006/0.006 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

执行

这里我们主要总结的是class文件在jvm中怎么跑起来的。

加载

在遇到下面几种情况的时候会去加载class文件:
  • 创建对象
  • 使用类的静态属性
  • Class.forName
  • 启动类
  • 初始化子类
大致分为三步完成:
  1. 加载:找到Class文件并读取里面的内容
  2. 连接
    1. 验证:确保Class文件的正确性
    2. 准备:对静态变量分配内存、设置默认值
    3. 解析:将符号引用替换成直接引用
  3. 初始化:为静态变量赋予正确的初始值。
和ClassLoader相关的东西可以看这里

解释和编译

生成的class文件中有字节码,这些字节码描述了方法的行为,总体上来看是以栈为基础的。

编译分为c1(client)和c2(server),刚开始代码是解释执行,当出现“热代码”的时候,会JIT会使用编译器将字节码编译成本地代码执行,在具体实现时有几个层次:
  1. 解释执行
  2. 编译成本地代码并进行简单优化
  3. 进行耗时比较长的、比较激进的优化
常见的优化技术有:
  1. 逃逸分析
  2. 同步消除
  3. 标量替换
多态等特性使得优化变得很复杂。和编译相关的常用参数有:
  • -XX:ReservedCodeCacheSize:设置代码缓存区的大小
  • -XX:CICompilerCount:设置编译器的线程数
  • -XX:-CITime:打印花在JIT上的时间
  • -XX:-PrintCompilation:当有方法被编译时,打印出相关信息

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值