1.什么内存垃圾
Java的内存,主要分配给栈、堆和方法区。
栈的内存是固定的,只与类结构有关,在运行前就是确定的,在方法或线程结束时就能回收。
堆和方法区的内存是动态的,比如接口有不同的实现类,内存都不一样,比如方法可能走不同的逻辑分支,内存也不一样。所以堆和方法区的内存,只能在运行期间确定,必须动态地分配和回收,这就会产生垃圾。
所以,Java的垃圾回收,针对的就是堆和方法区。
堆和方法区都是引用对象(基本类型都在栈里),所以要回收的,其实就是不再被人引用的对象,如果一个内存(堆或方法区中)中的对象,没有任何引用,那么这个对象就是无用的对象,需要回收,腾出内存。
如何判断垃圾
要判断一个对象是不是垃圾,主要检查它有没有被引用
2.什么是垃圾回收 GC (garbage collection)
手动gc, JDK提供了一个gc函数, 调用这个函数, 就完成对JVM虚拟机内存进行一次GC
System.gc();
自动gc, 当JVM虚拟机启动后, 后台会自动运行一个gc程序, 负责内存的gc操作
这个后台的gc程序会定时做gc操作, 或者是当JVM内存不够用的时候, 会立刻触发gc
JDK自带的jvisualvm工具,安装Visual GC插件
3.对象与指针的关系
同一个对象可以被多个指针同时指向
一个指针可以指向不同的对象, 但是同一时刻只能指向一个对象
代码示例
public class Demo01 {
//没有static修饰,data是成员变量,占用1024=1kb 1024*1024=1mb 1024*1024*100=100mb
static Demo01 demo1 =null;
byte[] data =new byte[1024*1024*100];
public static void main(String[] args) {
sleep(20);
test01();
sleep(10);
System.gc();
while (true){}
}
private static void test01() {
Demo01 demo01= new Demo01();
}
public static void sleep(int n){
for (int i = 1; i <= n; i++) {
System.out.println(i);
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
测试当对象有指针指向的时候, 对象不会被垃圾回收器回收
当对象没有指针指向的时候, 对象会被垃圾回收器回收, 会不会立即回收?
4. 指针是可以传递的
从栈中传递到堆区
例如: 领养宠物成功后, 将宠物对象的指针传递到宠物数组中进行存储
随机生成怪物对象后, 将怪物对象的指针传递到怪物数组中进行存储
从栈中传递到另一个栈中
例如: 宠物杀死怪物成功后, 调用宠物的killOk方法, 并把怪物的指针传递
到killOk方法中, 这样可以在killOk方法中通过怪物的指针获取到被杀死的怪物对象中的信息
5. 禁止直接操作对象中的属性, 用成员方法封装操作对象属性的过程
对象中的属性被private封装了
所有操作都应该提供成员方法, 用指针去调用成员方法来操作
例如: 当宠物攻击或被攻击, 怪物攻击或被攻击, 都应该提供对应的成员方法进行调用
6.JVM运行时概况
7.方法区Method Area
JDK1.8后改名为元空间Meta Space
各个线程共享区域
在JVM启动时被创建,并且物理内存可以不连续
大小可以固定也可以是动态扩展的
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类会出现OOM错误。
会随着JVM的关闭而释放这一区域的内存
栈区 Stack Area
- 每个线程都有一个stack区,stack中只保存基本数据类型的对象和自定义的对象引用(也就是地址值),对象都存储在heap中
- 每个stack中的数据(原始的基本数据类型和对象的引用)都是private的,其他的stack不能访问
- stack分为3个部分:基本数据类型变量区,执行环境上下文,操作指令区(存放操作指令)
- 由编译器自动分配释放,存放函数的参数值,局部变量的值等
堆区 Heap Area
- 并不保存对象的方法(方法是指令,保存在stack中)
- heap存储的全部是对象 ,每个对象包含一个与之对应的class信息(class的目的是得到操作的指令(指令也就是方法))
- jvm只有一个heap区,被所有的线程共享,heap中不存储基本的数据类型,只存储对象本身
- 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
他们之间的关系
8.JVM垃圾回收算法(GC)
标记 -清除算法
“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所以说它是最基础的收集算法,是因为后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。
它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
复制算法
“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,持续复制长生存期的对象则导致效率降低。
标记-整理算法
复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代收集算法
GC分代的基本假设:绝大部分对象的生命周期都非常短暂,存活时间短。
“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
Minor GC和Major GC/Full GC的区别
Minor.gc发生在新生代的gc
major.gc/full gc发生在老年区的gc 通常major.gc发生的时候都伴随着minor的发生
9.创建商品类Goods
创建一个Goods类 定义成员变量
属性 注意考虑商品属性的数据类型
商品名称 name
商品价格 price
商品描述 description
商品库存 stock
商品分类 category
商品状态 state
//定义成员变量
private String name;//商品名称
private double price;
private String description;
private int stock;
private String category;
private int state;
生成构造函数全选就好了
public Goods(String name, double price, String description, int stock, String category, int state) {
this.name = name;
this.price = price;
this.description = description;
this.stock = stock;
this.category = category;
this.state = state;
}
成员方法 show
作用打印商品对象信息
public void show(){
System.out.println("商品名称"+this.name);
System.out.println("商品价格"+this.price);
System.out.println("商品描述"+this.description);
System.out.println("商品库存"+this.stock);
System.out.println("商品分类"+this.category);
System.out.println("商品状态"+this.state);
}
最后直接生成get和set点击鼠标右键
选择get和set