Java的内存,主要分配给栈、堆和方法区。
栈的内存是固定的,只与类结构有关,在运行前就是确定的,在方法或线程结束时就能回收。
堆和方法区的内存是动态的,比如接口有不同的实现类,内存都不一样,比如方法可能走不同的逻辑分支,内存也不一样。所以堆和方法区的内存,只能在运行期间确定,必须动态地分配和回收,这就会产生垃圾。
要判断一个对象是不是垃圾,主要检查它有没有被引用。
简而言之就是已经使用完毕但是还没有释放的内存。
手动gc, JDK提供了一个gc函数, 调用这个函数, 就完成对JVM虚拟机内存进行一次GC
System.gc()
自动gc, 当JVM虚拟机启动后, 后台会自动运行一个gc程序, 负责内存的gc操作
这个后台的gc程序会定时做gc操作, 或者是当JVM内存不够用的时候, 会立刻触发gc
在jdk的bin目录下有jvisualvm.exe,右击以管理员方法运行,在它的工具选项的插件选项中的可用插件中选择安装Visual GC.
Minor GC 发生在新生代的GC, 速度很快
Major GC/Full GC发生在老年代的GC, 通常Major GC发生的时候都伴随着Minor GC的发生, 速度比较慢
示例:
public class Demo01 {
//没有static修饰,data是成员变量,占用100Mb
byte[] data = new byte[1024 * 1024 * 100];
static Demo01 demo = null; //点击运行时,类加载静态变量和静态代码块,出现在字节码的静态变量
public static void main(String[] args) { //类加载成功后,main()函数入栈执行
sleep(20); //slee入栈执行20秒后,出栈
test01(); //test01入栈,出栈
sleep(10);
System.gc(); //调用垃圾回收
System.out.println("finish");
while (true){
}//在这里进行死循环的目的是不让main函数结束执行
}
private static void test01() { //new Demo01,会去找Demo01的构造函数压栈执行 ---> new Demo01的时候会 new byte
Demo01 demo01 = new Demo01(); //test01 去堆里造对象,属性是data,data是有值的,是地址指向了一个数组
demo = demo01; //demo01在栈区中,demo在方法区中,把demo01赋给demo,因为test01在栈帧中,所以test01出栈后,demo01销毁
}
public static void sleep(int n){
for (int i = 1; i <= n; i++) {
System.out.print(i);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
Java jvisualvm运行截图
以上述所说的程序作为示例,在test01出栈后,通过System.gc()调用了垃圾回收机制。但是相应的内存空间没有被释放的原因是局部变量demo01被赋值给了demo,而demo是静态变量,不会因为test01的出栈而消失,所以此时依然有指针指向Demo01这个对象,所以这个对象的内存空间不会被垃圾回收机制释放。
在JVM虚拟机执行GC的时候, 所有的线程都会暂停运行, 有一句叫做"stop the world!"
如果堆区空间一直不能回收则会产生OutOfMemoryError: Java heap space
一个对象可以同时被多个指针指向;
一个指针可以指向不同的对象,但是同一时刻只能指向一个对象。
当对象有指针指向的时候, 对象不会被垃圾回收器回收;
当对象没有指针指向的时候, 对象会被垃圾回收器回收, 但是不会立即回收。
例如: 领养宠物成功后, 将宠物对象的指针传递到宠物数组中进行存储随机生成怪物对象后, 将怪物对象的指针传递到怪物数组中进行存储
例如: 宠物杀死怪物成功后, 调用宠物的killOk方法, 并把怪物的指针传递到killOk方法中, 这样可以在killOk方法中通过怪物的指针获取到被杀死的怪物对象中的信息
if (pkMonster.getHp() <= 0){
pkdog.killok(pkMonster);
break;
}
----------------------------------------------------
public void killok(Monster monster){
System.out.println(this.nickName + "成功击杀" + monster.getName());
System.out.println("获取经验值:" + monster.getExp());
this.exp += monster.getExp();
if (this.exp >= this.maxExp){
this.level++;
this.exp = this.exp - this.maxExp;
System.out.println(this.nickName + "升级到" + this.level + "级!");
}
}
对象中的属性被private封装了
所有操作都应该提供成员方法, 用指针去调用成员方法来操作
例如: 当宠物攻击或被攻击, 怪物攻击或被攻击, 都应该提供对应的成员方法进行调用
程序运行时大致可以分为三块,分别可以称之为方法区、栈区、堆区。
堆区是jvm中栈内存最大的一块区域,他在虚拟机启动时创建,是所有线程所共享的一块区域。堆的任务就是存放所有的对象实例,几乎所有的对象实例都储存在这里。他可以分为年轻代、永生代,同时Java堆也是GC工作的主要区域,有时候我们也叫他GC堆。
新生代在进行GC时,会向两个S区转移对象,但是当两个S区都已经没有空间,那就无法将新生代的对象再转移到S区,这时候就会分配担保机制将新生代中的对象直接晋升到老年代。
年轻代又可以分为三部分伊甸园区(Eden)、幸存者一区(S0)、幸存者二区(S1)默认情况下这三部分大小按照8:1:1(大小可调)分布,整个年轻代又占Java堆的1/3。
大多数的对象都在年轻代中创建,程序运行过程中很多对象都是朝生夕死。
小对象出生在伊甸园区, gc后, 如果能存活下来会被转移到幸存者区, 对象的年龄计数器会+1
当新生代的对象经历了15次回收后依然能够存活, 会进入老年代。
重对象/大对象一出生就在老年代。
虚拟机栈是每个Java方法的内存模型,虚拟机栈中元素叫做“栈帧”,每一个方法被执行的时候都会压入一个栈帧,执行完毕则出栈,这个栈帧里面存放着这个方法的局部变量表(包括参数)、操作栈、动态链接、方法返回地址。
我们需要知道的是,局部变量表是在编译的时候就确定大小了,我们在调用一个方法时,局部变量表的大小是已知且确定的,在方法执行的时候不会改变局部变量表的大小。
我们常说的Java内存中的栈内存一般就是指的局部变量表部分。如果变量是基本类型,会直接保存在这个区域,如果是引用类型,那会保存对象的引用地址。
元数据区又被称为方法区,也可以叫作永久代。
方法区内存储着字节码文件和常量池;
它用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,是各个线程共享的内存区域。
题目要求:
创建商品类Goods
属性 注意考虑商品属性的数据类型
商品名称 name
商品价格 price
商品描述 description
商品库存 stock
商品分类 category
商品状态 state
成员方法 show
作用打印商品对象信息
构造方法
setter/getter方法
创建测试类Demo
静态变量声明商品数组, 初始长度为10
静态变量控制台输入
添加商品方法1 - addGoods1() 使用构造方法初始化商品对象数据 注意: 数组长度不够了要提示去扩容
控制台接受用户输入商品信息
使用全参构造方法构造商品对象
商品对象存入商品数组中
添加商品方法 2 - addGoods2() 使用设值器set方法初始化商品对象数据 注意: 数组长度不够了要提示去扩容
控制台接受用户输入商品信息
使用无参构造方法构造商品对象
使用商品对象的设值器set方法存入商品数据到商品对象中
商品对象存入商品数组中
遍历商品方法 - display() 注意: 遇到数组中null的元素要判断, 防止出空指针异常
遍历商品数组, 取出每个商品对象, 调用对象的show()方法打印商品信息
商品数组扩容方法
main方法
使用do.while循环打印菜单
1. 添加商品1
2. 添加商品2
3. 查看商品
4. 商品数组扩容
5. 退出
接受用户输入的菜单编号
使用switch.case 判断用户选择的菜单, 调用对应的方法
Goods.java
package Goods;
public class Goods {
private String name;
private Integer price;
private String description;
private Integer stock;
private String category;
private String state;
public Goods() {
}
public Goods(String name, Integer price, String description, Integer stock, String category, String state) {
this.name = name;
this.price = price;
this.description = description;
this.stock = stock;
this.category = category;
this.state = state;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
Demo.java
package Goods;
import com.sun.org.apache.xerces.internal.impl.dv.XSSimpleType;
import java.util.Scanner;
public class Demo {
static Goods[] goodsArr = new Goods[10];
static Scanner sc = new Scanner(System.in);
static String name;
static Integer price;
static String description;
static Integer stock;
static String category;
static String state;
public static void main(String[] args) {
int choice;
do {
System.out.println("******商品管理系统********");
System.out.println("*******1.添加商品1*******");
System.out.println("*******2.添加商品2*******");
System.out.println("*******3.查看商品********");
System.out.println("*******4.商品数组扩容*****");
System.out.println("*******5.退出!**********");
System.out.println("请输入数字1~5进行选择:");
choice = sc.nextInt();
switch (choice){
case 1:
System.out.println("添加商品1");
addGoods1();
break;
case 2:
System.out.println("添加商品2");
addGoods2();
break;
case 3:
System.out.println("查看商品");
display();
break;
case 4:
System.out.println("商品数组扩容");
scale();
break;
case 5:
System.out.println("商品管理系统已退出!");
break;
default:
System.out.println("输入不合法!!!");
}
}while (choice != 5);
}
private static void scale() {
Goods[] goodsArr2 = new Goods[goodsArr.length + 10];
for (int i = 0; i < goodsArr.length; i++) {
goodsArr2[i] = goodsArr[i];
}
goodsArr = goodsArr2;
System.out.println("商品数组扩容+10,目前库存容量总数:" + goodsArr.length);
}
private static void addGoods2() {
if (goodsArr[goodsArr.length - 1] != null){
System.out.println("仓库已满请扩容!");
return;
}
input();
Goods goods = new Goods();
goods.setName(name);
goods.setPrice(price);
goods.setDescription(description);
goods.setCategory(category);
goods.setState(state);
for (int i = 0; i < goodsArr.length; i++) {
if (goodsArr[i] == null){
goodsArr[i] = goods;
break;
}
}
}
private static void display(){
for (int i = 0; i < goodsArr.length; i++) {
if (goodsArr[i] == null){
return;
}else {
show(goodsArr[i]);
}
}
}
private static void show(Goods goodArr) {
System.out.println("*****************");
System.out.println("商品名称:" + goodArr.getName());
System.out.println("商品价格:" + goodArr.getPrice());
System.out.println("商品描述:" + goodArr.getDescription());
System.out.println("商品库存:" + goodArr.getStock());
System.out.println("商品分类:" + goodArr.getCategory());
System.out.println("商品状态:" + goodArr.getState());
}
private static void addGoods1() {
if (goodsArr[goodsArr.length - 1] != null){
System.out.println("仓库已满请扩容!");
return;
}
input();
Goods goods = new Goods(name,price,description,stock,category,state);
for (int i = 0; i < goodsArr.length; i++) {
if (goodsArr[i] == null){
goodsArr[i] = goods;
break;
}
}
}
private static void input(){
System.out.println("请输入商品名称:");
name = sc.next();
System.out.println("请输入商品价格:");
price = sc.nextInt();
System.out.println("请输入商品描述:");
description = sc.next();
System.out.println("请输入商品库存:");
stock = sc.nextInt();
System.out.println("请输入商品分类:");
category = sc.next();
System.out.println("请输入商品状态:");
state = sc.next();
}
}