人是利益使然的动物,任何时候,找到为什么,再去明白是什么。
偶尔在公众号看所谓的“XX月最受欢迎的java开源项目”,有一个项目好像一直榜上有名,最近有时间才好好去看下这个项目到底是干嘛的。先来看看这个项目“宣称”要解决的问题:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?
    一看这些现象,“上生产之后突然报错”、“打完包后代码没有执行”(是不是编译出错)、“调试只能通过添加日志重新发布”、“开发环境无法重现”,哇一个个都是平时很经常遇到的问题。然后这个打着Alibaba开源的Java诊断工具,深受开发者喜爱旗号的开源项目–Arthas就开始引起了我研究的兴趣了。

    项目地址:https://github.com/alibaba/arthas


Arhas初体验

软件的安装和常用命令可以看项目里面的手册,在docker镜像里试用了一下,情况是能够正常启动成功。
启动命令:

1
docker exec -it {containerId} /bin/bash -c  'java -jar arthas-boot.jar'

进入容器并成功启动后,可以选择所要监控的java进程(当没有java进行时会启动失败)。
初始界面
如图所示,可以看到arthas先自动从阿里的maven库里下载了所需要的启动文件,在外网的环境下可以正常下载。在目前的生产环境离线的条件下,需要将maven库底下的包下载后,打镜像时添加到/root/.arthas/lib/3.0.5/arthas下。
Dockerfile

maven地址:https://repo1.maven.org/maven2/com/taobao/arthas/arthas-packaging

接着尝试了一些操作文档里的命令。以下场景均为预设。

  • 当打包完毕后,发布生产环境,逻辑没有实现。这时候可以利用反编译来查看JVM中加载的代码。

    1
    $ jad {ClassPath.ClassName}
  • 调试接口时,接口传参出参异常的查看。下面指令是指查看某个类某个方法第一个深度为2的入参。

    1
    $ watch {ClassPath.ClassName} {WayName} {params[0]} -x 2

watch结果
当然,在调试过程中发现,入参的函数也是可以调用的,如{params[0].getRemoteHost()}
arthas实现的“代码植入”,“代码实时反编译”,还有所谓跟JVM有关的信息,到底怎么做到的,JVM能够提供给我们什么信息,对于我们开发人员能带来什么样的提示。这些所有的所有,都要从好好了解JVM开始。


JVM

JVM,全称Java Virtual Machine,即Java虚拟机,它的作用就是让Java源代码被机器识别然后执行。
JVM的内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。
JVM内存布局
由此可见,方法区(元空间)和堆是线程共享的,而虚拟机栈,本地方法栈和程序计数器是线程隔离的。

堆(Heap)

Heap是OOM故障主要发源地,存储着几乎所有的实例对象,堆由垃圾收集器自动回收,占用的空间是所有内存区域中最大的。
可利用参数 -Xms -Xmx 进行堆内存控制。其中,-X表示它是JVM运行参数,ms指memory start,表示最小堆容量,mx指memory max,表示最大堆容量。
堆分为两大块,新生代和老年代,新生代又分为Eden区和两个Survivor区(from Survivor/to Survivor),三者的比例是8:1:1。

通常情况下,在生产环境中,JVM的Xms和Xmx设置成一样的大小,避免在GC(垃圾回收)后调整堆大小时带来额外的压力。

方法区(Method Area)/元空间(Metaspace)

方法区(Method Area)又名永久代(Perm区),它在启动时固定大小,很难进行调优,某些场景下,若动态类加载过多,容易产生Perm区的OOM(内存溢出),而解决问题的方法就是设定运行参数。
而元空间(Metaspace)在JDK8开始,取代了方法区,解决了上面的问题。

  • 方法区主要用于存放已经被虚拟机加载的类信息,如常量,静态变量。 这块区域也被称为永久代
    可利用参数 -XX:PermSize -XX:MaxPermSize 控制初始化方法区和最大方法区大小。
  • 元数据区在默认情况下,会根据使用情况动态调整,避免了在 jdk7 中由于加载类过多从而出现 java.lang.OutOfMemoryError: PermGen
    但也不能无限扩展,因此可以使用 -XX:MaxMetaspaceSize来控制最大内存。

    其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
    java -XX:PermSize -XX:MaxPermSize
    JDK6:java.lang.OutOfMemoryError: PermGen space
    JDK7/8:java.lang.OutOfMemoryError: Java heap space
    java -XX:MetaspaceSize -XX:MaxMetaspaceSize
    JDK8:java.lang.OutOfMemoryError: Metaspace

元空间与永久代之间最大的区别:元空间并不在虚拟机中,而是使用本地内存。
//TODO