Java底层

反解析节码指令
概念:javap.exe是jdk自带的反解析工具,它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。
CMD反解析:使用命令 javap .options.classes
options:反解析的选项,可使用 ‘java-help’ 来查看
classes:你要反编译的.class文件
IDEA反解析方式:
view -bytecode’来查看文件的字节码。
需要先运行过一次生成字节码文件之后才可以查看。

在这里插入图片描述
基本信息指定:
基本信息指定包括类文件的基本信息,JDK版本号,类的访问号标识符等
类的访问标识符有几种助记符

  • ACC_PUBLIC (0X001) :被public修饰。
  • ACC_FINAL(0X0010) : 被final修饰
  • ACC_SUPER(0X0020):允许使用Invokespecial字节码指令的新语句
  • ACC_INTERFAUE(0X0200): 是一个接口
  • ACC_ABSTRACT(0X0040):是抽象的,包括接口和抽象类
  • ACC_SYNTHETIC(0X1000): 这个类并非是由用户代码产生的
  • ACC_ANNOTATION(0X2000):这是一个注释类
  • ACC_ENUM(0X4000): 这是一个枚举类

字节码:
字节码文件前8行注释
对public int method();进行字节码解析

  • descriptor : () I :方法无参,返回值类型为int
  • flags :ACC_PUBLIC (0X001):被public修饰a
  • stack=4 :方法最大操作栈深为4
  • locals=1 :方法局部变量表大小为1slot
  • args_size=1:方法参数个数为1,包括隐藏参数this
  • 0: aload_0:将第1个引用数据类型局部变量压入栈顶1:dup:复制栈顶数值并将复制值压入栈顶
  • 2: getfield #2 //Field num:I’:获取成员属性(引用常量池中第二个常量),并将其之压 栈顶
  • 5: dup_x1:复制栈顶数值并将两个复制数值压入栈顶
  • 6: iconst_1:将int型字面 ‘1’ 压入栈顶
  • 7: iadd:将栈顶两值相加并将结果压入栈顶
  • 8:putfield #2 //Field num:I’:为成员属性(引用常量池中第二个常量)赋值为当前栈顶的值
  • 11: ireturn :返回int类型值。
  • LineNumberTable:行号表
    -line 10:0:源码中第1日行对应字节码中的第0条指令。
  • LocalVariableTable:局部变量表
    "Start=0:该局部变量从字节码的第1行开始可见- Length=12:该局部变量在12行字节码指令之内可见- "Slot=0`:该局部变量存储在局部变量表的e号位置- "Name=this:该局部变量的名称为this
    Signature=…/ByteCodeDemo:该局部变量的类型为ByteCodeDemo

常量池指令
概念:常量池 Constant pool中标识的是常量池信息,可以理解成class文件中的资源仓库,主要存储2大类常量
字节码:常量池内容解析

  • #1 = Methodref #4.#18 / /java/lang/0bject. “” 😦)v
    unordered list: 常量池中的第1个常量是一个方法,引用… 方法
  • #2=Fieldref#3.#19//com/joezhou/classload/ByteCodeTest.num:工
    unordered list: 常量池中的第2个常量是一个属性,引用…ing;
  • #3 = Class #20 /com/joezhou/classload/ByteCodeTest*
    unordered list:-常量池中的第3个常量是一个类,引用了第…符号引用。
  • #5 = Utf8 num
    常量池中的第5个常量是一个字符串。
  • “#18 = NameAndType #7:#8/ / “”😦)V`
    常量池中的第18个常量是一个变量名和变量类型拼成的字符串常量,引用了第7个和第8个常量,最终可以拼成后面的注释内容。

方法代码指令·
概念:常量池内容之后的是方法描述信息,在字节码中被放在一个{}集合里面,其中比较重要的概念有:

  • stack : 最大操作数栈,JVM运行时会根据这个值来分配栈帧中的操作栈深度。
  • locals : 局部变量表大小,catch块中的异常变量,方法参数,this,局部变量等全存这里。
  • unordered list : 局部变量表的单位为Slot (4byt…以重用的。
  • args_size : 方法参数个数,包括隐藏参数this。
  • LineNumberTable:行号表,每个方法都有一个行号表,记录了字节码偏移量(源码行号与字节码行号之间的对应关系)
  • line 1:0 : 源码中的1号位置代码对应的是字节码中的0号位置的指令。
  • 局部变量表的单位为Slot (4byte),Slot是JVA为局部变量分配内存时所使用的最小单位。
  • locals的大小并不一定等于所有局部变量所占的Slot之和,因为局部变量中的Slot是可以重用的。
  • args_size : 方法参数个数,包括隐藏参数this.
  • LineNumberTable : 行号表,每个方法都有一个行号表,记录了字节码偏移量(源码行号与字节码行号之间的对应关系)
  • line 1:0 :源码中的1号位置代码对应的是字节码中的0号位置的指令。
  • LocalVariableTable : 局部变量表,每个方法都有一个局部变量表,描述帧栈中局部变量与源码中定义的变量之间的关系。
  • Start :该局部变量从字节码的哪一行开始可见。
  • Length :该局部变量在多少行代码之内可见。
  • Slot :该局部变量在局部变量表的几号位置。
  • Name :该局部变量的名称。
  • Signature :该局部变量的类型。

JVM入门概念
概念:JVN是java虚拟出来的一块内存(运行java的场所),但它不是具体的产品,它只是一套规范标准,而HotSpot才是JDK1.8中,Oracle公司生产的一个JVM的具体实现;
JVM采用了那种实现,可以通过java -version命令查看。
规范和实现的关系,可以对比理解成接口和实现类的关系。
运行时数据区:程序运行时,ClassLoader会将class文件加载到JVW中的运行时数据区(Runtime Data Area)中,该区域通常被分为五部分,如果将内存比作一个"小区",那么:

  • Java栈Stack:相当于小区的物业室,空间小,功能少,但访问方便。
  • Java堆Heap:相当于小区的住宅区,空间大,功能多,但访问麻烦。
  • 方法区Method Area:相当于小区的公告板,谁都可以用,且只存在一个。
  • PC寄存器Program Counter Registen :相当于小区的保安,监视并登记每个访问者(线程)的位置。
  • 本地方法栈Native Method Stack:相当于小区的开发商,主要负责和操作系统打交道,因为使用的不是Java语言的原因,一般我们不关心,而且在3DK1.8版本中,此区域已经被整合到了Java栈中。
    在这里插入图片描述
    PC寄存器
    概念:
  • PC寄存器也叫程序计数器,它占用很小的一块内存空间,它由JVM直接管理,不需要我们管理。-PC寄存器是线程私有的,即每个线程中都有独立的Pc寄存器。
  • PC寄存器记录的是字节码指令的行号位置,其所在的线程会按照它的指示进行工作。
  • PC寄存器只适用于非本地方法,如果线程执行的是本地方法(Native方法),则PCc寄存器始终为-PC寄存器初始值为e,当线程执行任务时,详细的步骤模拟如下:
    1.字节码解释器(专门操作字节码的一个家伙)开始工作…
    2.PC寄存器自增:0=>1
    3.所在线程执行第1行字节码指令…
    4.第1行字节码指令执行完毕…
    5.PC寄存器自增:1 =>2
    6.所在线程执行第2行字节码指令…
    7.第2行字节码指令执行完毕…
    8…
    –此区域是唯一一个在JVM规范中没有规定出现`OutOfMemoryError(OOM)情况的区域。

栈内存
概念:JVM栈是负责执行java方法的一块内存区域,被每一个线程所私有,随线程的创建而创建。

  • 一个线程中的每个方法在执行时都会创建一个对应的栈帧Stack Frame,一个方法开始被调用到执行完成的过程,就对应着一个栈帧在JVM栈中从入栈到出栈的过程(FILO First-In-Last-Out)。
  • 不同线程之间所包含的栈帧是不允许存在相互调用的。
  • 在一条活动线程中,只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,被称为当前栈帧。
    本地方法栈:负责执行本地方法的一块内存区域,使用的是‘native’方法,底层并不是java语言,不用我们操心,而且不同的JVM实现对栈区域的实现方式是不同的,比如‘HotSpot’就把本地方法栈和JVM栈合二为一

栈帧
概念:一个栈帧中主要包含:局部变量表,操作数栈,动态链接和方法出口。

  • 局部变量表‘LocalVariableTable’,也叫本地变量表,是一种线性表的数据结构。
  • 局部变量表存放的是在编译期就可知的各种基本数据类型和对象引用,一般指的就是方法的参数或者方法内的局部变量。
  • 一个方法需要在帧中分配多大的局部变量空间是在编译期就可以确定的。
  • 方法在运行期间局部变量表的大小是不会改变的。可以使用‘-g:none’或’-g:vans` 命令来取消或生成这项信息,如果选择不生成它,那么当别人引用这个方法时,将无法获取到参数名称,取而代之的是arge,arg1这样的占位符。
  • 操作数栈:用来保存计算过程的中间结果,如5*6,会得出一个30,这个30是个临时数据,并没有装在任何一个变量中,会暂时存放在这里。
  • 动态链接:存放的是对象的引用。
  • 帧数据区:装着访问常量池的指针和访问异常处理表的指针。

栈内存异常
概念:

  • 栈内存中的变量在出了作用域后会自动销毁,所以不用担心它的回收问题。
  • 栈的大小可以固定也可以动态拓展,或者可以通过Xss参数来设定。
    栈内存中可能会出现两种内存异常:
  • 如果线程请求的栈调用深度大于虚拟机所允许的深度,将抛出StackOverflowError导常。
  • 如果虚拟机栈在动态扩展时无法申请到足够的内存,抛出OutOfMemoryError异常。|
    栈的深度是由栈的内存空间决定的,请求的栈越深,也即是已使用的栈的空间占用越大,所以上面规定的两种异常是有重叠之处的,一种异常也可能会导致另外一种异常的发生。
/**
 * @author liusichang
 */
public class StackDepthTest {
    //测试栈帧深
    private static int stackFrameCount=0;
    public static void main(String[] args) {
        try {
            method();
        }catch (Throwable e){
            System.out.println("current stack depth:"+stackFrameCount);
            e.printStackTrace();
        }
    }
    public static void method(){
        stackFrameCount++;
        method();
    }
}

堆内存
概念: Java堆是JVM所管理的内存中最大的一块,所有线程共享,几乎99%对象实例都是在这里
分配内存的,因为它也是GC管理的主要区域,所以也被称做GC堆。

  • 内存碎片: Java堆是一块不连续的内存空间,且实例所需的内存大小在类加载完成后就可以确定下来,为实例分配内存空间相当于把一块确定大小的内存从Java堆中划分出来,这个划分在内存中是随机的,就可能会导致已经使用过的内存和空闲的内存椎互交错,所以这就需要一个列表来维护,来记录哪些内存块是可用的,哪些内存块已经被实例占用了,在这个过程中也很可能会产生一些内存碎片,不过碎片问题就是GC该考虑的问题了。
  • 内存溢出:Java堆是如果没有足够的内存空间完成对象实例的分配,并且堆也无法再扩展,将会抛出OutOfMemoryError (OOM)异常(新实例造不出来)
  • 内存泄露:程序在申请内存后,无法释放已申请的内存空间,内存泄露会导致内存资源耗光,通俗的说就是实例占着内存空间无法归还给系统。(旧实例回收不了)
    测试: Java堆是用于存储实例的,所以只要不断地创建对象,来把Java堆填满,并且保证垃圾回收机制不能清除这些对象就可以模拟出Java堆内存的溢出。
/**
 * @author liusichang
 */
public class test {
    public static void main(String[] args) {
        while (true){
            new Thread(){
                @Override
                public void run(){
                    while (true){
                        System.out.println("go");
                    }
                }
            }.start();
        }
    }
}

在这里插入图片描述
使用Xms20M -Xmx2限制堆内存大小为20M。

方法区
概念:方法区是线程共享的运行时内存区域,用于存储已经被JVM加载过的类的信息(如类的名称、类的修饰符信息、类的成员信息、常量池等)、常量、静态变量、即时编译器编译后的代码等数据等。
方法区和VM类似,只是一个规范而不是一个实现,JDK7之前,Hotspot使用永久代来实现方法区,永代和堆相互隔离,永久代的大小在启动JVM的时候可以设置一个固定不变的值,JDK1.8取消了永久代的概念,Hotspot使用元空间来实现方法区,仍然与堆空间不相连。JVWN规范中把方法区描述为堆的一个逻辑部分,也叫做“非堆”,也是为了和Java堆区分开来,其实可以它理解成存放各种信息的区(小区公告板)。

  • 方法区和Java堆一样,也不需要连续的内存空间,在JVM的实现中,也是可以选择固定大小或者可扩展,并且还可以选择不实现垃圾回收,因为这个区域用到回收的地方很少,但这个区域同样会出现内存泄漏的问题。
  • 当方法区无法满足内存分配时,抛出OutOfMemoryError (OOM)异常。

运行时常量池
概念:运行时常量池是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息之外,还有一个常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类被加载后,进入方法区的运行时常量池中。

  • 运行时常量池用于存放编译期的常量信息、方法和属性编译期的符号引用和运行期的直接引用。
  • 运行时常量池具有动态性,常量并不一定是在编译期才会被放入该常量池,在运行期间也可能有新的常量进入池中,比如调用String类的intern(),这个方法的作用就是的作用就是将某个实例从堆内存中移动到常量池中的String池中。

String池
概念:String池,也叫StringTable,底层的数据结构是hashset,不允许重复。DK1.7中,String池从永久代移动到了堆中。
凡是被双引号引起来的值都会先去String池中查找,如果存在就拿出来直接使用,如果不存在就将它放入到池中,然后再使用。
下面那行代码分别创建了几个对象(堆池栈都算)

String str1=new String("aaa";//栈中一个str1,堆中一个 new,String池中一个
String str2=new String ("aaa");//栈中一个str2,堆中一个,String池中没有
System.out.println(str1+str2);//常量池中一个

下面代码结果是什么

String a="abc";
String b="abc";
System.out.println(a==b);//T:都在String池中,且不允许重复
String c=new Sring("abc");
System.out.println(a==c);//F:一个在String池中,一个在堆中
System.out.println(a==c.intern());//T:c.intern作用就是将String池中调用

代码重用池
概念:数值在[-128,127]之间,返回指IntegerCache.cache中已经存在的对象的引用,即-128到127之间的数据,都在代码重用池中存放,在使用的时候,会直接从池中获取。在这里插入图片描述

/**
 * @author liusichang
 */
public class test2 {
    public static void main(String[] args) {
        int aa=1;
        int bb=5000;
        Integer a=1;
        Integer b=5000;
        Integer c=1;
        Integer d=2;
        Integer e=3;
        Integer f=3;
        Integer g=321;
        Integer k=321;
        Long l=3L;
        Long h=2l;
        System.out.println(e==f);//T ef都来自代码重用池
        System.out.println(g==k);//F gk中来自不同的堆内存
        System.out.println(e==(c+d));//T 都来自代码重用池
        System.out.println(e.equals(f));//T 值和类型都一样
        System.out.println(f==(c+d));//T cd来自代码从用池
        System.out.println(l.equals((c+d)));//F 值相同但是类型不同
        System.out.println(l==(c+h));//T等号两端如果出现表达式,则触发自动拆箱
        System.out.println(a==aa);//T
        System.out.println(b==bb);//T
    }
}

等号两端如果出现表达式,则触发自动拆箱。

直接内存
概念:直接内存并不在JVM管理的内存区域内,也不是JVM规范中定义的内存区域,而是直接使用外部c主机的物理内存,这在一些场景中(如文件复制)可以提高性能,但是在使用过程中,也要注意主机内存小的限制(包括物理和系统级的限制)和垃圾回收工作。否则也会抛出OutOfMemoryError异常。

垃圾回收机制(GC)
概念:java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,由于有个GC,java中的对象不再有作用域的概念,只有对象的引用有作用域,垃圾回收可以有效的防止内存泄漏,有效的使用空闲的内存。
所谓程序的运行,其实就是变量不断地在内存中的申请领地,进行活动,最后交还领地给JWN内存的一个过程,假设程序最后不交还领地,则会导致内存垃圾越来越多,Java对内存垃圾的处理方式是全自动的,JDK提供了一个垃圾回收员(Garbage Collection),它会不定时跑到你的程序中去回收内存垃圾,消除了程序员手动清理垃圾的烦恼。

JVM栈的GC
概念:内存的划分中,程序计数器、JVN栈和本地方法栈都是随线程生和死的,栈中的栈帧随着方法的调用有序的进栈和出栈,每个栈帧上分配的内存大小在类结构确定时就已知了,所以这些区域内存的分配和回收都是具有确定性的,很容易回收,当方法调用结束或者线程结束,占用的内存就可以被回GC收。

方法区的GC
概念:虽然JVW规范中没有要求对方法区进行垃圾回收,但是一些虚拟机,如HotSpot成拟机仍然实现了方法区的垃圾回收,方法区的垃圾主要是废弃的常量和无用的类。我们知道方法区中有一些常量池,如字符串常最池,如果系统中不存在引用常量池中常量的引用,那么在内存紧张的时候,这些常量就应该被<废弃回收,常量池中的其他类(接口)、方法、字段、符号引用也是如此。
判断常量是否应该被废弃的方法比较简单,而判断一个类是无用的类,则需要满足下面三个条件

  • 该类的所有实例都已经被回收了,即Java堆内存中没有该类的对象实例。
  • 加载该类的类加载器classLoader已经被回收了。
  • 该类对应的java.lang.Class对象在任何地方都没有被引用,也即无法通过反射访问该类。
  • 但满足了上述这些条件,也不是说这个类就要被非回收不可,我们是可以通过设置虚拟机参数进行控制的。

JAVA堆的GC
在Java推中,每个类需要的内存都可能不一样,一个方法中多个分支需要的内存也可能不一样。这些都只有在运行期才能知道创建哪些对象,所以这部分内存的分配和回收都是动态的,垃圾回收也主要是对这部分的内存进行回收。

垃圾判定算法
概念:怎么样钓实例算是垃圾?判断一个实例对象的生死很难,就像有些人虽然活着,但其实早就死了,一些人虽然死了,但永远活在我们心中,文p<艺可以扯淡,但是编程必须严谨,所以我们必须要有判断实例对象生或者死的方法。

引用计数算法
概念:

  • 当一个对象被创建时,为这个对象实例分配一个变量,该变量计数设置为1:
  • 每当有一个地方引用它时,计数器加1。
  • 每当一个对它的引用失效时,计数器减1。
  • 当计数器的值为时,就说明不存在对它的引用了,它就可以去死了(不是立刻死亡,而是等待GC啥时候心情好,就过来干死它)。-一个对象被回收时,这个对象所引用的其他任何对象的引用计数器也会减一。
  • 优点:简单高效直接。
  • 缺点:无法检测和解决实例和实例之间的循环引用的问题。如实例A和实例B都是同一个类的实例,A中引用B,B中引用A,则A和B都不能被回收。

使用‘verbose:gc -XX:+PrintGCDetails’运行参数可以看到详细的cc情况,可以发现空间变小,说明程序依然执行了也说明jdk1.3使用的不是引用计数回收策略。
在这里插入图片描述

2.2可达性分析算法

概念:可达性分析算法也叫根搜索算法,是从离散数学中的图论引进而来的,算法把所有的引用关系看成是一张图,从一个根节点GC -Root开始,寻找它所引用的节点,找到它所引用的节点后,继续寻找这个节点所引用的其他节点,当所有被引用的节点都寻找完毕,剩下的没有被引用过的节点,认为是垃圾。

  • GC-Roots,是一个特殊的对象,且绝对不能被其他对象引用(所以不会出现循环引用的问题)。
  • 虚拟机栈(栈帧本地变量表)中引用的对象。
  • 方法区中静态属性引用的对象。
  • 方法区常量引用的对象。
  • 本地方法栈中(Native 方法)引用的对象。
    从根节点开始向下搜索实例对象,搜索所走的路径称为是引用链,当升个对象从根节点开始找不到任何一条引用链时,就说明这个2对象可被回收,可达性分析算法的本质就是判断某个实例对象是否有可达的引用链。

jdk1.8使用的是可达性分析算法。

finalize():Object类中的一个GC相关的方法。

  • 在可达性分析算法中,即使是不可达的对象,也并非是要立即执行死刑,它们暂时处于死缓状态。
  • finalize()能够让实例拥有一次死里逃生的机会,但每个实例都有且只有一次机会,从第二次开始就必死无疑.
  • 不可达的实例第一次会被标记并进行一次筛选,筛选的条件就是这个finalize(),当实例没有重写finalize(),或者finalize()被JVM调用了而这个实例还没有被回收的时候,finalize()都不会被执行。
  • 如果判定需要执行finalize(),这个实例就会被放到一个队列中,由低优先级的单独线程(刽子手)执行实例中的finalize()。
  • 如果在finalize()中,该实例突然被引用链上其他的可达实例关联了,那么这个对象就可以被移出这个即将回收的队列,从而死里逃生。

final finally finaliza 的区别

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值