jvm指令介绍及在线debug工具原理

简介:

jvm指令、内存模型介绍

Java Virtual Machine:

To understand the details of the bytecode, we need to discuss how a Java Virtual Machine (JVM) works regarding the execution of the bytecode. JVM is a platform-independent execution environment that converts Java bytecode into machine language and executes it. A JVM is a stack-based machine. Each thread has a JVM stack which stores frames. A frame is created each time a method is invoked, and consists of an operand stack, an array of local variables, and a reference to the runtime constant pool of the class of the current method.

image.png | center | 414x308

Stack Based Virtual Machines:
We need to know a little about stack based VM to better understand Java Bytecode. A stack based virtual machine the memory structure where the operands are stored is a stack data structure. Operations are carried out by popping data from the stack, processing them and pushing in back the results in LIFO (Last in First Out) fashion. In a stack based virtual machine, the operation of adding two numbers would usually be carried out in the following manner (where 20, 7, and “result” are the operands):

image | center

jvm指令原理

jvm指令list说明,https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

查看字节码的两种方式

  • idea->view->show bytecode(安装方式Preferences->Plugins->搜“Bytecode Viewer”)
    image.png | center | 1052x334
  • ASMifier asm指令方式
    ClassReader reader = new ClassReader(Hello.class.getName());

reader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(System.out)), ClassReader.SKIP_DEBUG);

静态方法与非静态方法

静态方法与非静态方法的调用核心区别在于,本地变量数组的第一个元素是否是this,比如hello、hi两个空方法

public class Hello {
    public void hello() {
    }
    public static void hi() {
    }
}

public class com/aliyun/demo/app/Hello {

  public hello()V
   L0
    LINENUMBER 9 L0
    RETURN
   L1
    //非静态方法本地变量的第一个值是“this”
    //本地变量     名称  类型                       作用范围   index
    LOCALVARIABLE this Lcom/aliyun/demo/app/Hello;  L0 L1     0
    MAXSTACK = 0
    MAXLOCALS = 1
  public static hi()V
   L0
    LINENUMBER 12 L0
    RETURN
    MAXSTACK = 0
    MAXLOCALS = 0
}

operand stack vs local variables

初步了解操作栈、本地变量数组的“变化过程”

   public int hello(String name, String desc) {
        int i = 100;
        int j = 200;
        return i + j;
    }

 public hello(Ljava/lang/String;Ljava/lang/String;)I
   L0
    LINENUMBER 9 L0
    //向栈写入100
    BIPUSH 100   //operand stack[100],local variables[this,name,desc]
    //把栈顶的元素存储到本地数组的index=3的位置
    ISTORE 3     //operand stack[],local variables[this,name,desc,100]
   L1
    LINENUMBER 10 L1
    //向栈写入200
    SIPUSH 200   //operand stack[200],local variables[this,name,desc,100]
    //存储栈顶的元素到本地数组的index=4的位置
    ISTORE 4     //operand stack[],local variables[this,name,desc,100,400]
   L2
    LINENUMBER 11 L2
    //把本地数组index=3的元素加载的操作栈中
    ILOAD 3     //operand stack[100],local variables[this,name,desc,100,200]
    //把本地数组index=4的元素加载的操作栈中
    ILOAD 4     //operand stack[100,200],local variables[this,name,desc,100,200]
    //把栈顶的两个元素相加后,结果写入栈中
    IADD        //operand stack[300],local variables[this,name,desc,100,200]
    IRETURN     //返回的值在operand stack的顶
   L3
    LOCALVARIABLE this Lcom/aliyun/demo/app/Hello; L0 L3 0
    LOCALVARIABLE name Ljava/lang/String; L0 L3 1
    LOCALVARIABLE desc Ljava/lang/String; L0 L3 2
    LOCALVARIABLE i I L1 L3 3
    LOCALVARIABLE j I L2 L3 4
    MAXSTACK = 2 //operand stack的max(length)=2
    MAXLOCALS = 5 //本地存储有5个值
}
  • 初始的local variables中的值分别为可选的this对象(静态方法没有)及方法参数,故可通过在方法开始时读取local variables获取方法的入参、修改入参等
  • 在方法结束时返回值位于栈顶,可以读取“operand stack”获取方法的返回值

try catch异常

有人听过try catch会比较耗时吗?通过字节码分析一下原因

  public void hello(double i) {
        try {
            System.out.println(i);
        } catch (RuntimeException e) {
            throw e;
        }
    }

  public hello(D)V
    TRYCATCHBLOCK L0 L1 L2 java/lang/RuntimeException//try开始
   L0
    LINENUMBER 10 L0
    //获取System.out类型为PrintStream的对象并写入栈中
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;//operand stack[out]、local variables[this,i]
    //加载本地变量index=1的值到栈中
    DLOAD 1 //operand stack[out,i]、local variables[this,i]
    //调用out的虚函数println,会消耗栈中的out、i,所以调用完栈空了(如果有返回值会写入栈中)
    INVOKEVIRTUAL java/io/PrintStream.println (D)V //operand stack[]、local variables[this,i]
   L1
    LINENUMBER 13 L1
    GOTO L3//没有异常跳到L3,即catch结束的位置
   L2//catch开始
    LINENUMBER 11 L2
    //新的frame,如果没有异常,那么就不会开新的frame
   FRAME SAME1 java/lang/RuntimeException//F_SAME1 representing frame with exactly the same locals as the previous frame and with single value on the stack ( nStack is 1 and stack[0] contains value for the type of the stack item).
   //before astore->operand stack[exception],local variables[this,i]
    ASTORE 3 //operand stack[],local variables[this,i,,exception]
   L4
    LINENUMBER 12 L4
    ALOAD 3 //operand stack[exception],local variables[this,i,,exception]
    ATHROW 
   L3
    LINENUMBER 14 L3
   FRAME SAME//Represents a compressed frame with exactly the same locals as the previous frame and with an empty stack.
             //operand stack[],local variables[this,i,,exception]
   L5
    LOCALVARIABLE e Ljava/lang/RuntimeException; L4 L3 3
    LOCALVARIABLE this Lcom/aliyun/demo/app/Hello; L0 L5 0
    LOCALVARIABLE i D L0 L5 1
    MAXSTACK = 3
    MAXLOCALS = 4
}

总结:通过TRYCATCHBLOCK、GOTO、LABEL方式可以方法增加try catch,也可以把异常吃掉等
问题:本例中local variables中index为2的没有被使用,why?

jvm指令总结

  • 方法正常返回是operand stack栈顶为的返回值
  • 方法的入参存储在local variables中,静态方法从0开始、非静态从1开始
  • try catch指令,提供了拦截异常的一种方式,甚至吃掉异常

字节码工具asm应用示例

cglib与javassist是高级的字节码工具,asm与bcel是低级的字节码工具(支持java字节码指令),低级的工具会更灵活,故选择asm来作为分析,asm文档参见
http://asm.ow2.org/doc/developer-guide.htmlhttp://download.forge.objectweb.org/asm/asm4-guide.pdf

spring模式的字节码增强原理

如何把增强的代码加入到当前的jvm中?spring已经给了我们答案,但是有两个疑问:

  • HelloService$EnhancerByCGLIB$41503a7e,为什么不是HelloService ?

答:HelloService已经被AppClassLoader加载(通常情况下),不能重复加载相同名称的class

  • bean的实例的getSuperClass()的值为什么是HelloService ?

答:HelloService helloService=(HelloService)getBean(“helloService”),如果动态生成的class不是被增强的class子类,强制转化报异常

  • AppClassLoader并未暴露defineClass方法,如何加载的增强类?

答:通过反射调用classloader.defineClass即可

故spring本质是对代理类做了一个子类,故除了对“代理类字节码”增强外,还需要修改“代理类字节码”的parent、及class name

asm简化版本的spring模式字节码增强

  • DemoClassVisitor:对方法的增强,入参、返回值、异常、耗时
  • ChangeParentClassVisitor:修改类的parent
  • ChangeNameClassVisitor:修改类名称

注:asm代码主要使用了visitor、责任链模式,理解了这两个设计模式代码读起来比较容易

//入参分别为HelloService,HelloServiceQyf,返回增强后的HelloServiceQyf的字节码
  private static byte[] transform(String oldName, String newName) throws IOException {
        ClassReader cr = new ClassReader(oldName);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);

        final String oldTypeName = oldName.replace(".", "/");
        final String newTypeName = newName.replace(".", "/");

        ClassVisitor visitor = null;
        //对方法增强
        visitor = new DemoClassVisitor(cw, DemoAdvice.class);
        //修改parent
        visitor = new ChangeParentClassVisitor(visitor, oldTypeName);
        //增加rename逻辑
        visitor = new ChangeNameClassVisitor(visitor, oldTypeName, newTypeName);
        cr.accept(visitor, ClassReader.EXPAND_FRAMES);
        return cw.toByteArray();
    }

DemoClassVisitor详解

ChangeParentClassVisitor、ChangeNameClassVisitor不涉及jvm指令代码相对简单,故我们重点介绍DemoClassVisitor,它通过继承asm的工具类AdviceAdapter,分别实现onMethodEnter、onMethodExit,核心代码如下:

@Override  
public MethodVisitor visitMethod(int access, final String name, final String desc, String signature,  
String[] exceptions) {  
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);  
if (name.contains("")) {  
return mv;  
}

    return new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {

        private final Label tryCatchStart = new Label(), tryCatchEnd = new Label(), exceptionHandler = new Label();

        @Override
        public void onMethodEnter() {
            {//try start
                visitTryCatchBlock(tryCatchStart, tryCatchEnd, exceptionHandler, "java/lang/Exception");
                visitLabel(tryCatchStart);
            }
            {//方法开始植入计数起始
                visitLdcInsn(name);
                visitMethodInsn(Opcodes.INVOKESTATIC, adviceName, "start", "(Ljava/lang/String;)V", false);
            }
            {//获取方法入参值
                Type types[] = Type.getArgumentTypes(desc);
                if (types.length == 0) {
                    return;
                }
                for (int i = 0; i < types.length; i++) {
                    visitLdcInsn(name);
                    visitLdcInsn(i);
                    loadArg(i);
                    visitMethodInsn(Opcodes.INVOKESTATIC, adviceName, "args",
                        "(Ljava/lang/String;ILjava/lang/Object;)V", false);
                }
            }
        }

        @Override
        public void onMethodExit(int opcode) {

            if (opcode != RETURN) {//如果有返回值,提取出来
                int returnIndex = newLocal(Type.getReturnType(desc));
                dup();
                storeLocal(returnIndex);
                visitLdcInsn(name);
                loadLocal(returnIndex);
                visitMethodInsn(Opcodes.INVOKESTATIC, adviceName, "result",
                    "(Ljava/lang/String;Ljava/lang/Object;)V", false);
            }
            {//方法调用结束,打印方法名,入参
                visitLdcInsn(name);
                visitMethodInsn(Opcodes.INVOKESTATIC, adviceName, "commit", "(Ljava/lang/String;)V", false);
            }

            {//try end
                visitLabel(tryCatchEnd);
                Label exitMethodLabel = new Label();
                visitJumpInsn(GOTO, exitMethodLabel);
                visitLabel(exceptionHandler);//operand stack[exception]
                dup();//operand stack[exception,exception]
                visitLdcInsn(name);//operand stack[exception,exception,name]
                swap();//operand stack[exception,name,exception]
                visitMethodInsn(Opcodes.INVOKESTATIC, adviceName, "exception",
                    "(Ljava/lang/String;Ljava/lang/Object;)V", false);
                //operand stack[exception]
                mv.visitInsn(Opcodes.ATHROW);
                visitLabel(exitMethodLabel);
            }
        }
    };
}

获取方法的入参代码

onMethodEnter中增加如下code:

  Type types[] = Type.getArgumentTypes(desc);//desc=(D)V,double入参,无返回值
  if (types.length == 0) {
      return;
  }
  for (int i = 0; i < types.length; i++) {//参数迭代
      //向操作栈写入name
      visitLdcInsn(name);//operand stack[name]
      //向操作栈写入参数的index
      visitLdcInsn(i);//operand stack[name,i]
     //从本地数组加载方法的index对应的入参
     loadArg(i);//operand stack[name,i,arg_i]
       //调用args(String method, int index, Object object),消耗掉name、i、arg_i
      visitMethodInsn(Opcodes.INVOKESTATIC, adviceName, "args",
          "(Ljava/lang/String;ILjava/lang/Object;)V", false);
  }

获取方法的异常

onMethodEnter中最上面增加如下code:

visitTryCatchBlock(tryCatchStart, tryCatchEnd, exceptionHandler, "java/lang/Exception");
visitLabel(tryCatchStart);

onMethodExit中最下面增加如下code:

 visitLabel(tryCatchEnd);//try end
 Label exitMethodLabel = new Label();
 visitJumpInsn(GOTO, exitMethodLabel);//方法没有异常直接到exitMethodLabel位置
 visitLabel(exceptionHandler);//catch start,operand stack[exception]
 dup();//复制operand stack一个top元素,operand stack[exception,exception]
 //operand stack增加方法名
 visitLdcInsn(name);//operand stack[exception,exception,name]
 //交互operand stack顶的两个元素位置
 swap();//operand stack[exception,name,exception]
 //调用静态方法exception(String method, Object object)
 visitMethodInsn(Opcodes.INVOKESTATIC, adviceName, "exception",
     "(Ljava/lang/String;Ljava/lang/Object;)V", false);
 //operand stack[exception]
 mv.visitInsn(Opcodes.ATHROW);
 visitLabel(exitMethodLabel);

获取方法的返回值

onMethodExit中增加如下code:

 if (opcode != RETURN) {//RETURN表示方法为void,故不需要抓取返回值
     int returnIndex = newLocal(Type.getReturnType(desc));//operand stack[result]
     //不能把原来的result用了,需要复制一份
     dup();//operand stack[result,result]
     storeLocal(returnIndex);//operand stack[result]
     visitLdcInsn(name);//operand stack[result,name]
     loadLocal(returnIndex);//operand stack[result,name,result]
     //调用result(String method, Object object)
     visitMethodInsn(Opcodes.INVOKESTATIC, adviceName, "result",
         "(Ljava/lang/String;Ljava/lang/Object;)V", false);
     //operand stack[result]
}

运行效果

原始的HelloService代码

public class HelloService {

    public void sayHi(String message) {
        System.out.println("--------------sayHi start---------------");
        System.out.println(this.getClass().getName() + ".sahHi:" + message);
        System.out.println("--------------sayHi end------------------");
    }
    public void exception() {
        int i = 1 / 0;
    }
}

正常流程

 @Test
    public void sayHi() throws Exception {
        HelloService hello = ApplicationContext.getBean(HelloService.class.getName());
        hello.sayHi("hi");
    }
控制台输出
--------------sayHi start---------------
com.aliyun.demo.app.HelloServiceYqf.sahHi:hi
--------------sayHi end------------------

--------------asm 字节码增强信息 start----------- 
调用方法:sayHi
方法入参:hi
响应耗时:6 ms
--------------asm 字节码增强信息 end----------- 
  • 注意虽然调用的是HelloService,但实际运行是HelloServiceYqf
  • 通过增强,我们获取导入入参、响应时间(该方法为void故无返回值)

异常流程

    @Test
    public void exception() {
        try {
            HelloService hello = ApplicationContext.getBean(HelloService.class.getName());
            hello.exception();
        } catch (Exception e) {
            System.out.println("main-> " + e.toString());
        }
    }

控制台输出,主线程(main->)与增强都获取了异常信息
--------------asm 字节码增强信息 start----------- 
调用方法:exception
方法入参:无
异常信息:java.lang.ArithmeticException: / by zero
响应耗时:0 ms
--------------asm 字节码增强信息 end----------- 

main-> java.lang.ArithmeticException: / by zero

吃掉异常流程

注释掉onMethodExit中异常代码的dup及mv.visitInsn(Opcodes.ATHROW)代码即可,被拦截的方法必须没有返回值,否则需要在代码里生成默认值

控制台输出

--------------asm 字节码增强信息 start----------- 
调用方法:exception
方法入参:无
异常信息:java.lang.ArithmeticException: / by zero
响应耗时:0 ms
--------------asm 字节码增强信息 end----------- 
  • 注意main开头的打印代码未出现在控制台

instrument

Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。
在 Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能。在 Java SE 5 中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作。但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了 instrument 的应用。而 Java SE 6 的新特性改变了这种情况,通过 Java Tool API 中的 attach 方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。
更为详细的参见https://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html

打包方式

偷懒把依赖的jar包也打进去,方便一些

 <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>attached</goal>
                        </goals>
                        <phase>package</phase>
                        <configuration>
                            <descriptorRefs>
                                <descriptorRef>jar-with-dependencies</descriptorRef>
                            </descriptorRefs>
                            <archive>
                                <manifestEntries>
                                    <Premain-Class>com.aliyun.demo.app.AgentLauncher</Premain-Class>
                                    <Agent-Class>com.aliyun.demo.app.AgentLauncher</Agent-Class>
                                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                                </manifestEntries>
                            </archive>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

agent启动类

代码只用到了DemoClassVisitor,并不需要替换名称、及父类,因为Java Tool API提供了“直接修改已加载的class字节码”的能力,和“类spring”方式不同

//mvn clean package -Dmaven.test.skip=true,打包
public class AgentLauncher {

    public static void agentmain(String args, Instrumentation inst) {

        final String regex = StringUtils.isEmpty(args) ? "com.aliyun.demo.+Service" : args;
        System.out.println("filter:" + regex);

        inst.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> {
            if (!Pattern.matches(regex, className)) {
                return null;
            }
            System.out.println("transform class:" + className);
            ClassReader reader = new ClassReader(classfileBuffer);
            ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
            DemoClassVisitor visitor = new DemoClassVisitor(writer, DemoAdvice.class);
            reader.accept(visitor, ClassReader.EXPAND_FRAMES);
            return writer.toByteArray();
        }, true);

        for (Class cls : inst.getAllLoadedClasses()) {
            try {
                if (Pattern.matches(regex, cls.getName())) {
                    inst.retransformClasses(cls);
                }
            } catch (UnmodifiableClassException e) {
                e.printStackTrace();
            }
        }
    }
}

attach目标jvm(windows无效)

VirtualMachine及VirtualMachineDescriptor是非标准包,在windows下是没有的!

@Test
    public void agentTest() throws Exception {
        //jarName需要修改,改成自己的打包生成路径
        String jarName = "/Users/ghost/works/hello/target/hello-1.0-SNAPSHOT-jar-with-dependencies.jar";
        String targetClass = "HelloTest";
        //需要增强的package正则
        String filter = "com.aliyun.demo.+Service";
        for (VirtualMachineDescriptor vd : VirtualMachine.list()) {
            String displayName = vd.displayName();
            if (displayName.contains(targetClass)) {
                VirtualMachine virtualMachine = VirtualMachine.attach(vd);
                virtualMachine.loadAgent(jarName, filter);
                virtualMachine.detach();
            }
        }
    }

运行效果

运行方式:先运行agentTarget启动待增强的jvm、再运行agentTest动态增强

   @Test
    public void agentTarget() {
        HelloService helloService = new HelloService();
        while (true) {
            helloService.sayHi("hi");
            try {
                Thread.sleep(10 * 1000L);
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }
        }
    }
    @Test
    public void agentTest() throws Exception {
        //mvn clean package -Dmaven.test.skip=true , jarName需要修改,改成自己的打包生成路径
        String jarName = "/Users/ghost/works/hello/target/hello-1.0-SNAPSHOT-jar-with-dependencies.jar";
        String targetClass = "HelloTest";
        //需要增强的package正则
        String filter = "com.aliyun.demo.+Service";
        for (VirtualMachineDescriptor vd : VirtualMachine.list()) {
            String displayName = vd.displayName();
            if (displayName.contains(targetClass)) {
                VirtualMachine virtualMachine = VirtualMachine.attach(vd);
                virtualMachine.loadAgent(jarName, filter);
                virtualMachine.detach();
            }
        }
    }

agent启动前的console输出

--------------sayHi start---------------
com.aliyun.demo.app.HelloService.sayHi:hi
--------------sayHi end------------------
--------------sayHi start---------------
com.aliyun.demo.app.HelloService.sayHi:hi
--------------sayHi end------------------

agent启动后的console输出

filter:com.aliyun.demo.+Service
transform class:com/aliyun/demo/app/HelloService
--------------sayHi start---------------
com.aliyun.demo.app.HelloService.sayHi:hi
--------------sayHi end------------------
--------------asm 字节码增强信息 start----------- 
调用方法:sayHi
方法入参:hi
响应耗时:5 ms
--------------asm 字节码增强信息 end----------- 
  • 注意是HelloService.sayHi,不是HelloServiceQyf.sayHi

总结

  • 简单介绍jvm的部分指令,详情参见https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
  • spring的代码增强的原理,本质是通过classloader反射+asm字节码增强,去做一个目标类的子类加载到当前jvm
  • 在线字节码工具原理,通过java agent方式动态的修改jvm被增强的class,实现获取入参、异常、返回值、耗时等
相关文章
|
10月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
1693 4
|
9月前
|
Oracle Java 关系型数据库
JVM深入原理(一+二):JVM概述和JVM功能
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行。
259 0
|
9月前
|
Arthas 存储 Java
JVM深入原理(三+四):JVM组成和JVM字节码文件
目录3. JVM组成3.1. 组成-运行时数据区3.2. 组成-类加载器3.3. 组成-执行引擎3.4. 组成-本地接口4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
174 0
|
9月前
|
存储 安全 Java
JVM深入原理(五):JVM组成和JVM字节码文件
类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析。
141 0
|
9月前
|
Arthas Java 测试技术
JVM深入原理(六)(一):JVM类加载器
目录6. JVM类加载器6.1. 类加载器-概述6.2. 类加载器-执行流程6.3. 类加载器-分类(JDK8)6.3.1. JVM底层实现的类加载器6.3.1.1. 启动类加载器6.3.2. Java代码实现类的加载器6.3.2.1. 扩展类加载器6.3.2.2. 应用程序类加载器6.4. 类加载器-Arthas查看类加载器
181 0
|
9月前
|
Java 关系型数据库 MySQL
JVM深入原理(六)(二):双亲委派机制
自定义类加载器打破双亲委派机制的方法:复写ClassLoader中的loadClass方法常见问题:要加载的类名如果是以java.开头,则会抛出安全性异常加载自定义的类都会有一个共同的父类Object,需要在代码中交由父类加载器去加载自定义类加载器不手动指定parent会默认指定应用类加载两个自定义类加载器加载同一个类会被认为是两个对象,只有相同的类加载器+想通的类限定名才会被认为是一个对象。
322 0
|
9月前
|
存储 安全 Java
JVM深入原理(七)(一):运行时数据区
栈的介绍:Java虚拟机栈采用栈的数据结构来管理方法调用中的基本数据,先进后出,每一个方法的调用使用一个栈帧来保存栈的组成:栈:一个线程运行所需要的内存空间,一个栈由多个栈帧组成栈帧:一个方法运行所需要的内存空间活动栈帧:一个线程中只能有一个活动栈帧栈的生命周期:栈随着线程的创建而创建,而回收会在线程销毁时进行栈的执行流程:栈帧压入栈内执行方法执行完毕释放内存若方法间存在调用,那么会压入被调用方法入栈,执行完后释放内存,再执行当前方法,直到执行完毕,释放所有内存。
191 0
|
9月前
|
存储 缓存 安全
JVM深入原理(七)(二):运行时数据区
堆的作用:存放对象的内存空间,它是空间最大的一块内存区域.栈上的局部变量表中,可以存放堆上对象的引用。静态变量也可以存放堆对象的引用,通过静态变量就可以实现对象在线程之间共享。堆的特点:线程共享:堆中的对象都需要考虑线程安全的问题垃圾回收:堆有垃圾回收机制,不再引用的对象就会被回收方法区的概述:方法区是存放基础信息的位置,线程共享,主要包括:类的元信息:保存了所有类的基本信息运行时常量池:保存了字节码文件中的常量池内容静态常量池:字节码文件通过编号查表的方式找到常量。
132 0
|
9月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
236 0
|
9月前
|
算法 Java 对象存储
JVM深入原理(八)(二):垃圾回收
Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为StopTheWorld简称STW,如果STW时间过长则会影响用户的使用。一般来说,堆内存越大,最大STW就越长,想减少最大STW,就会减少吞吐量,不同的GC算法适用于不同的场景。分代回收算法将整个堆中的区域划分为新生代和老年代。--超过新生代大小的大对象会直接晋升到老年代。
233 0