JAVA - 学习记录第六天

目录

一、面向对象的内存分析

(一)虚拟机内存模型(了解)

(二)程序执行过程的内存分析(掌握)

 (三)参数传值机制(掌握)

二、垃圾回收机制(Garbage Collection)(了解)

(一)垃圾回收原理和算法

(二)分代垃圾回收机制

(三)JVM调优和Full GC

(四)开发中容易造成内存泄漏的操作

三、this、static关键字

(一)this(掌握)

(二)static(掌握)

(三)静态初始化块(了解)

四、包机制(package、import)

(一)包package(掌握)

(二)导入类import(掌握)

(三)静态导入static import(了解)


一、面向对象的内存分析

(一)虚拟机内存模型(了解)

从属于线程的内存区域(栈、计数器)

JVM 的内存划分中,有部分区域是线程私有的,有部分是属于整个 JVM 进程;我们将 这部分归为一类。

  1. 程序计数器(Program Counter Register),在 JVM 规范中,每个线程都有自己的 程序计数器。这是一块比较小的内存空间,存储当前线程正在执行的 Java 方法的 JVM 指令 地址,即字节码的行号。如果正在执行 Native 方法,则这个计数器为空。
  2. Java 虚拟机栈(Java Virtal Machine Stack),同样也是属于线程私有区域,每个线 程在创建的时候都会创建一个虚拟机栈,生命周期与线程一致,线程退出时,线程的虚拟机 栈也回收。虚拟机栈内部保持一个个的栈帧,每次方法调用都会进行压栈,JVM 对栈帧的 操作只有出栈和压栈两种,方法调用结束时会进行出栈操作。该区域存储着局部变量表,编 译时期可知的各种基本类型数据、对象引用、方法出口等信息。
  3. 本地方法栈(Native Method Stack)与虚拟机栈类似,本地方法栈是在调用本地 方法时使用的栈,每个线程都有一个本地方法栈。

②堆(heap)

堆(Heap),几乎所有创建的 Java 对象实例,都是被直接分配到堆上的。堆被所有的 线程所共享,在堆上的区域,会被垃圾回收器做进一步划分,例如新生代、老年代的划分。 Java 虚拟机在启动的时候,可以使用“Xmx”之类的参数指定堆区域的大小。

③方法区(Method Area)

方法区与堆一样,也是所有的线程所共享,存储被虚拟机加载的元(Meta)数据,包 括类信息、常量、静态变量、即时编译器编译后的代码等数据。

方法区是一种 java 虚拟机的规范。由于方法区存储的数据和堆中存储的数据一致,实 质上也是堆,因此,在不同的 JDK 版本中方法区的实现方式不一样。 JDK7 以前,方法区就是堆中的“永久代”。 JDK7 开始去“永久代”,把静态变量、字符串常量池都挪到了堆内存中。

DK8 以后,“永久代”不存在了。存储的类信息、编译后的代码数据等已经移动到了 MetaSpace(元空间)中,元空间并没有处于堆内存上,而是(直接内存)直接占用的本 地内存(NativeMemory)。

④运行时常量池(Run-Time Constant Pool)(方法区中)

这是方法区的一部分。常量池主要存放两大类常量:

  1. 字面量(Literal),如文本字符串、final 常量值。
  2.  符号引用,存放了与编译相关的一些常量,因为 Java 不像 C++那样有连接的过程, 因此字段方法这些符号引用在运行期就需要进行转换,以便得到真正的内存入口地址。

⑤直接内存(Direct Memory)

直接内存并不属于 Java 规范规定的属于 Java 虚拟机运行时数据区的一部分。Java 的 NIO 可以使用 Native 方法直接在 java 堆外分配内存,使用 DirectByteBuffer 对象作为这 个堆外内存的引用。

(二)程序执行过程的内存分析(掌握)

①虚拟机栈(简称:栈)的特点如下:

  1. 栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变 量、操作数、方法出口等)。
  2. JVM 为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变 量等)
  3. 栈属于线程私有,不能实现线程间的共享!
  4. 栈的存储特性是“先进后出,后进先出”。
  5. 栈是由系统自动分配,速度快!栈是一个连续的内存空间!

②堆的特点如下:

  1. 堆用于存储创建好的对象和数组(数组也是对象)。
  2. JVM 只有一个堆,被所有线程共享。
  3. 堆是一个不连续的内存空间,分配灵活,速度慢!

③方法区(又叫静态区,也是堆)特点如下:

  1. 方法区是 JAVA 虚拟机规范,可以有不同的实现。i. JDK7 以前是“永久代” 。ii. JDK7 部分去除“永久代”,静态变量、字符串常量池都挪到了堆内存中。iii. JDK8 是“元数据空间”和堆结合起来。
  2. JVM 只有一个方法区,被所有线程共享!
  3. 方法区实际也是堆,只是用于存储类、常量相关的信息!
  4. 用来存放程序中永远是不变或唯一的内容。(类信息【Class 对象,反射机制中会 重点讲授】、静态变量、字符串常量等)

 (三)参数传值机制(掌握)

①基本数据类型参数的传值

传递的是值的副本。 副本改变不会影响原件。

②引用类型参数的传值

传递的是值的副本。但是引用类型指的是“对象的地址”。因此,副本和原参数都指向了同一个“地址”,改变“副本指向地址对象的值,也意味着原参数指向对象的值也发生了“改变”。

测试:

class Person {
    String name;
    int age;
    public void show(){
        System.out.println("姓名:"+name+",年龄:"+age);
    }
}
public class TestPerson {
    public static void main(String[ ] args) {
// 创建p1对象
        Person p1 = new Person();
        p1.age = 24;
        p1.name = "张三";
        p1.show();
// 创建p2对象
        Person p2 = new Person(this);//this可写可不写,隐式参数,默认传值
        p2.age = 35;
        p2.name = "李四";
        p2.show();
    }
}

内存分配简图:

过程:

堆里的所有参数变量都是java默认值,直到main(栈帧)里创建的p1(对象)赋予指向0x11(地址)变量的值,若赋值有字符串(如“张三”李四”,字符串在Java里是“类”),其存储在方法区,且拥有隐藏的地址,字符则直接传递在”堆“划分的地址区域里(0x11),p2同理。在栈中创建内存p2.show(栈帧),指向对应的地址区域(0x12),调用方法区中的show(方法),将传递的值输出。程序结束。

结论:

  1. 同一类的每个对象有不同的成员变量存储空间。 
  2. 同一类的每个对象共享该类的方法。

二、垃圾回收机制(Garbage Collection)(了解)

(一)垃圾回收原理和算法

①内存管理

  • Java 的内存管理很大程度指的就是:堆中对象的管理,其中包括对象空间的分配和释放。
  • 对象空间的分配:使用 new 关键字创建对象即可。
  • 对象空间的释放:将对象赋值 null 即可。垃圾回收器将负责回收所有”不可达”对象 的内存空间。 ·

②垃圾回收过程

任何一种垃圾回收算法一般要做两件基本事情:

  1. 发现无用的对象
  2. 回收无用对象占用的内存空间。

        垃圾回收机制保证可以将“无用的对象”进行回收。无用的对象指的就是没有任何变量 引用该对象。Java 的垃圾回收器通过相关算法发现无用对象,并进行清除和整理。

③垃圾回收相关算法

        1. 引用计数法

        堆中的每个对象都对应一个引用计数器,当有引用指向这个对象时,引用计数器加1,而当指向该对象的引用失效时(引用变为 null),引用计数器减 1,最后如果该对象的引用计算器的值为 0 时,则 Java 垃圾回收器会认为该对象是无用对象并对其 进行回收。优点是算法简单,缺点是“循环引用的无用对象”无法别识别。

        2. 引用可达法(根搜索算法)

        程序把所有的引用关系看作一张图,从一个节点GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找 完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。

(二)分代垃圾回收机制

分代垃圾回收机制,是基于这样一个事实:不同的对象的生命周期是不一样的。因此, 不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。我们将对象分为三种状 态:年轻代、年老代、持久代。同时,将处于不同状态的对象放到堆中不同的区域。 JVM 将堆内存划分Eden、Survivor 和 Tenured/Old 空间。

  1. 年轻代。所有新生成的对象首先都是放在 Eden 区。 年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象,对应的是 Minor GC,每次 Minor GC 会清理年轻代的内 存,算法采用效率较高的复制算法,频繁的操作,但是会浪费内存空间。当“年轻代” 区域存放满对象后,就将对象存放到年老代区域。
  2. 年老代。在年轻代中经历了 N(默认 15)次垃圾回收后仍然存活的对象,就会被放到年老代 中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。年老代对象越来越 多,我们就需要启动 Major GC 和 Full GC(全量回收),来一次大扫除,全面清理年轻 代区域和年老代区域。
  3.  永久代。用于存放静态文件,如 Java 类、方法等。持久代对垃圾回收没有显著影响。JDK7 以前就是“方法区”的一种实现。JDK8 以后已经没有“永久代”了,使用 metaspace 元数据空间和堆替代。

  • Minor GC: 用于清理年轻代区域。Eden 区满了就会触发一次。
  • Minor GC。清理无用对象,将有用 对象复制到“Survivor1”、“Survivor2”区中。 ·Major GC: 用于清理老年代区域。
  • Full GC: 用于清理年轻代、年老代区域。 成本较高,会对系统性能产生影响。

(三)JVM调优和Full GC

在对 JVM 调优的过程中,很大一部分工作就是对于Full GC 的调节。如下原因可能导致Full GC:

  1. 年老代(Tenured)被写满
  2. 持久代(Perm)被写满
  3. System.gc()被显式调用
  4. 上一次 GC 之后 Heap

(四)开发中容易造成内存泄漏的操作

如下四种情况时最容易造成内存泄露的场景:

  1. 创建大量无用对象。比如,我们在需要大量拼接字符串时,使用了String而不是StringBuilder。
  2. 静态集合类的使用 。像 HashMap、Vector、List 等的使用最容易出现内存泄露,这些静态变量的生命周期 和应用程序一致,所有的对象 Object 也不能被释放。
  3. 各种连接对象(IO 流对象、数据库连接对象、网络连接对象)未关闭 。IO 流对象、数据库连接对象、网络连接对象等连接对象属于物理连接,和硬盘或者网 络连接,不使用的时候一定要关闭。
  4.  监听器的使用不当。释放对象时,没有删除相应的监听器

其他要点:

  1. 程序员无权调用垃圾回收器
  2. 程序员可以调用 System.gc(),该方法只是通知 JVM,并不是运行垃圾回收器。尽量少用,会申请启动 Full GC,成本高,影响系统性能。
  3. finalize 方法,是 Java 提供给程序员用来释放对象或资源的方法,但是尽量少用。

三、this、static关键字

(一)this(掌握)

①对象创建的过程和 this的本质

        构造方法是创建 Java 对象的重要途径,通过 new 关键字调用构造器时,构造器也确实 返回该类的对象,但这个对象并不是完全由构造器负责创建。创建一个对象分为如下四步:

  1. 分配对象空间,并将对象成员变量初始化为 0 或空
  2. 执行属性值的显式初始化
  3. 执行构造方法
  4. 返回对象的地址给相关的变量

        this 的本质就是“创建好的对象的地址”! 由于在构造方法调用前,对象已经创建。 因此,在构造方法中也可以使用 this 代表“当前对象

②this 最常的用法:

  1. 在程序中产生二义性之处,应使用 this 来指明当前对象;普通方法中,this 总是指向调用该方法的对象。构造方法中,this 总是指向正要初始化的对象。
  2. 使用 this 关键字调用重载的构造方法,避免相同的初始化代码。但只能在构造方法中用,并且必须位于构造方法的第一句。
  3. this 不能用于 static 方法中。

(二)static(掌握)

        在类中,用 static 声明的成员变量为静态成员变量,也称为类变量。 类变量的生命周期和类相同,在整个应用程序执行期间都有效。它有如下特点:

  1. 为该类的公用变量,属于类,被该类的所有实例共享,在类被载入时被显式初始化。
  2. 对于该类的所有对象来说,static 成员变量只有一份。被该类的所有对象共享!!
  3. 一般用“类名.类属性/方法”来调用。(也可以通过对象引用或类名(不需要实例化)访问静态成员。)
  4. 在 static 方法中不可直接访问非 static 的成员。

测试:

public class test17 {
        int id; // id
        String name; // 账户名
        String pwd; // 密码
        static String company = "北京天安门"; // 公司名称
        public test17(int id, String name) {
            this.id = id;
            this.name = name;
        }
        public void login() {
            System.out.println("登录:" + name);
        }
        public static void printCompany() {
// login();//调用非静态成员,编译就会报错
            System.out.println(company);
        }
        public static void main(String[ ] args) {
            test17 u = new test17(101, "李白");
            test17.printCompany();
            test17.company = "杜甫";
            test17.printCompany();
        }
}

内存分配简图:

(三)静态初始化块(了解)

构造方法用于对象的初始化!静态初始化块,用于类的初始化操作!在静态初始化块中 不能直接访问非 static 成员。

静态初始化块执行顺序(学完继承再看这里):

  •  上溯到 Object 类,先执行 Object 的静态初始化块,再向下执行子类的静态初始化块, 直到类的静态初始化块为止。
  • 构造方法执行顺序和上面顺序一样!!

四、包机制(package、import)

(一)包package(掌握)

        包机制是 Java 中管理类的重要手段。 开发中,我们会遇到大量同名的类,通过包我们很容易对解决类重名的问题,也可以实现对类的有效管理。 包对于类,相当于文件夹对于文件的作用。

package 的使用有两个要点:

  1. 通常是类的第一句非注释性语句。
  2. 包名:域名倒着写即可,再加上模块名,便于内部管理类。
Java中常用的包说明
java.lang包含一些 Java 语言的核心类,如 String、Math、Integer、System 和 Thread, 提供常用功能。
java.swt包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类 被用来构建和管理应用程序的图形用户界面(GUI)。
java.net包含执行与网络相关的操作的类。
java.io包含能提供多种输入/输出功能的类。
java.util包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。

(二)导入类import(掌握)

注意要点:

  1. Java 会默认导入 java.lang 包下所有的类,因此这些类我们可以直接使用。
  2. 如果导入两个同名的类,只能用包名+类名来显示调用相关类: java.util.Date date = new java.util.Date();

(三)静态导入static import(了解)

        静态导入(static import)是在 JDK1.5 新增加的功能,其作用是用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值