Java查看字节码&内存分配&垃圾回收详解

一.查看字节码

1.AndroidStudio查看.java文件对应的.class文件

 

.java文件 

public class Test112233 {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 9;
                int j = 10;
                int sum = i + j;
                LoggerUtils.log("9+10=" + sum);
            }
        }).start();
    }

}

.class文件

public class Test112233 {
    public Test112233() {
    }

    public static void main(String[] args) {
        (new Thread(new Runnable() {
            public void run() {
                int i = 9;
                int j = 10;
                int sum = i + j;
                LoggerUtils.log("9+10=" + sum);
            }
        })).start();
    }
}

2.根据.class文件查看字节码

使用 javap -c命令

3. 字节码文件

4.名词解释

JVM提供了5种方法调用指令

<1> invokestatic:该指令用于调用静态方法,即使用 static 关键字修饰的方法。

<2> invokespecial:该指令用于三种场景:调用实例构造方法,调用私有方法(即private关键字修饰的方法)和父类方法(即super关键字调用的方法)。

<3> invokeinterface:该指令用于调用接口方法,在运行时再确定一个实现此接口的对象。

<4> invokevirtual:该指令用于调用虚方法(就是除了上述三种情况之外的方法)。

<5> invokedynamic:在运行时动态解析出调用点限定符所引用的方法之后,调用该方法;在JDK1.7中推出,主要用于支持JVM上的动态脚本语言(如Groovy,Jython等)。

二.内存分配

1.Java内存区域

Java内存区域大致可以划分为四部分。分别是程序计数器栈(Stack)堆(Heap)方法区

其中 程序计数器栈(Stack) 是随 线程启动而生线程结束而灭的,也就属于线程私有

堆(Heap)方法区 是由 JVM启动时创建 所有线程共享的。

如下图

2.四部分分别介绍

<1> 程序计数器

    我们知道所有程序代码在最底层都是要被转化为机器指令才能够被电脑识别并执行。当前程序不在是执行1+1或简单的打印字符串,而是要完成更加复杂的功能时,其所包含的指令也就更多,那么为了按逻辑顺序执行这些指令就需要一个计数器来帮助一条条提取指令或指出下一条指令的位置。这里说的是CPU中的程序计数器的概念,换成JVM 程序计数器,其所承担的任务是基本一样的,只需要将指令换成字节码行号即可。

   JVM的字节码解释器通过改变程序计数器的当前值,来提取下一条需要执行字节码的行号(既被编译为二进制的java代码)。所有的完成分支,循环,跳转,异常处理,线程恢复等的功能都是依赖它来完成的。

   另外,由于Java的多线程特性(通过轮流切换并分配CPU的使用权,CPU在同一时间也只会执行一个线程中指令),为了在线程切换后能够恢复到正确位置,所以每个线程都有自己的独立的 程序计数器,这就是所谓的线程私有。

<2> 栈(Stack)

    这里说的栈(Stack)是指虚拟机栈 (在JVM规范中,栈分为虚拟机栈也就是执行Java方法时用到的,还有本地方法栈,两者区别在与本地方法栈为执行Native方法服务,Sun公司的jvm 称为HotSpot,它将虚拟机栈与本地方法栈合二唯一了)。

   方法被执行时,方法体内局部变量(其中包括基本数据类型对象的引用)都在上创建。并在方法执行结束这些局部变量所持有的内存将会自动被释放。该内存空间可以被重新使用。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

   举例

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        method();
    }

    private void method() {
        int num1 = 3;
        int num2 = 7;
        int result = num1 + num2;
        Log.d("MainActivity", "result----:" + result);
    }
}

此Activity中,method方法中的num1,num2,num3,变量都是存储在栈内存中的。生命周期随着方法的执行而开始,随着方法执行完成而结束。变量所占据的内存自动释放。

说明:Android开发过程中,如果一个变量可以设置成局部变量,而你却把它声明成一个全局变量的话。AndroidStudio会有警告的。如下图

<3> 堆(Heap)

 堆的唯一作用就是存放所有对象实例(由 new 创建的对象)数组成员变量(和类一起创建)的。它是线程共享的,是JVM所有管理的内存中最大的一块,也是垃圾收集器GC管理的主要区域。

Java堆中还可以细分为:新生代老年代;再细致一点的有Eden空间,From Survivor空间,To Survivor空间等。

举例

public class MainActivity extends AppCompatActivity {

    private final int num1 = 3;
    private PeopleBean peopleBean = new PeopleBean();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        method1();
        method2();
    }

    private void method1() {
        int num2 = 7;
        int result = num1 + num2;
        Log.d("MainActivity", "result----:" + result);
    }

    private void method2() {
        int num2 = 17;
        int result = num1 + num2;
        Log.d("MainActivity", "result----:" + result);
    }
}

此Activity中,成员变量num1和new出来的peopleBean对象,都存在与堆内存中。这些变量占据的内存在不使用时将会由 Java 垃圾回收器来负责回收。

<4> 方法区

方法区也叫静态存储区。主要存放静态数据全局 static 数据常量。这块内存程序编译时就已经分配好,并且在程序整个运行期间都存在。与堆一样方法区也是线程共享的。

三.Java垃圾回收

1.简介

由上述可知,Java垃圾回收机制主要针对的内存区域是 堆区

Java技术中 自动内存管理 最终可以归结为自动化地解决了两个问题:给对象分配内存 以及 回收分配给对象的内存

四.如何确定一个对象是否可以被回收

如何判断一个对象可以被回收呢?常用的有两个算法。

1.引用计数算法

引用计数算法  是  判断对象的引用数量  来决定  对象所占据的内存是否可以被回收

引用计数算法是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数

当一个对象被创建时,且将该对象实例分配给一个引用变量,该对象实例的引用计数设置为 1。当任何其它变量被赋值为这个对象的引用时,对象实例的引用计数加 1。

比如:a = b,则b引用的对象实例的计数器加 1

当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数减 1。特别地,当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器均减 1。任何引用计数为0的对象实例可以被当作垃圾收集。

2.可达性分析算法

可达性分析算法  是  判断对象的引用链是否可达  来决定  对象所占据的内存是否可以被回收。

可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,通过一系列的名为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的,如下图所示。在Java中,可作为 GC Root 的对象包括以下几种:

<1> 虚拟机栈(栈帧中的局部变量表)中引用的对象。

<2> 方法区中类静态属性引用的对象。

<3> 方法区中常量引用的对象。

<4> 本地方法栈中Native方法引用的对象。

图解

五.垃圾回收算法

1.标记清除算法

标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收。

如下图

2.复制算法

复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

如下图

3.标记整理算法

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代)

如下图

4.分代收集算法

对于一个大型的系统,当创建的对象和方法变量比较多时,堆内存中的对象也会比较多,如果逐一分析对象是否该回收,那么势必造成效率低下。分代收集算法是基于这样一个事实:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。当代商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。Java堆内存一般可以分为新生代、老年代和永久代三个模块。

如下图

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值