【Day17-JVM&GC】

目录

一、JVM

二、GC垃圾回收

三、堆外内存(了解

一、JVM

1、JVM是运行在操作系统之上的。Scala语言也是运行在JVM之上。

2、JVM其实是一个标准,规范,符合这个规范的软件都可以称之为java 虚拟机。

BEA JRockit --> BEA 有一个服务器非常的有名 WebLogic(tomcat)。

IBM J9 ---> 某某某行业方案解决专家 (IBM小型机)。

3、JVM内存模型

1)ClassLoader 类加载器

所有的java文件,都必须编译为class文件 Hello.java --> Hello.class

所有的class文件要想运行,必须放入虚拟机中,通过我们的类加载器,加载进去。

类文件被加载,需要符合我们的双亲委派策略

一个类的class文件,只能被加载一次。加载的过程中有很多个加载器。而且这些加载器是有顺序的。

第一个加载器:Bootstrap ClassLoader --> <JAVA_HOME>\lib (String Date SimpleDateFormat)

第二个加载器:Extensioin ClassLoader --> 拓展加载器,只加载拓展的jar --> `<JAVA_HOME>\lib\ext`

第三个加载器:Application ClassLoader --> 如果用户没有自己定义类加载器,默认使用这个。

Student.java -->Student.class 加载开发人员编写的java代码(jar)

第四个加载器:用户自定义加载器(只有用户写了才会有这个加载器)

所有的class文件,在jvm中都是独一份的。

双亲委派策略:

一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托

双亲委派策略的好处:

Student.java --> Student.class
String.java  --> String.class
        
比如我们也可以自定编写一个String类,报名和类名都跟java自带的一模一样,但是我们会发现我们自己写的String类压根加载不了,原因是BootstrapClassLoader 已经加载过了。我们自己写的,不会重复加载。
这是java语言设计的一种安全机制。

拓展:双亲委派策略是否可以打破?如何打破?--《java面试速成手册》

2)本地方法接口(native interface)

当我们去java代码中追源码,看到native 修饰的方法,就是底部了,再追就是C++ 了。

我们可以发现Object类中有很多方法是使用native修饰,并且没有方法体,那说明这些方法超出了Java的范围。 所以底层实现需要使用JNI--->Java Native Interface。

native修饰的方法就是告诉java本身,此时需要调用外部的本地类库即C/C++类库来执行这个方法

3) java内存模型中的运行区

分为5部分:栈、本地方法栈、堆、寄存器、方法区

以上图,橘黄色是线程共享的,灰色的是线程私有的。垃圾回收的是线程共享的部分。

1、本地(native)方法栈:其实就是存放java代码中存在的native方法的。
2、方法区(静态方法区): 静态方法、静态变量、class类信息
   方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。
存放在方法区的:静态变量+常量+类信息(构造方法/接口定义)+运行时常量池存在方法区中
ps:只要是被线程私有独享的一律没有回收,只有是线程共享才能有回收。
所谓的垃圾回收回收的是java虚拟机中的方法区和堆。堆占(99%),方法区(1%)
3、程序计数器:
    代码运行到了哪一行,需要一个东西记下来,这个东西就是程序计数器。
    A线程执行到了23行代码,突然被B线程抢走了,A线程再次执行的时候就从23行以后执行。
    
栈 管运行,堆 管存储
4、栈(java的栈)
   栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。
   一个方法(Main)调用了A方法,先把Main压栈,A 方法放在Main方法上面,A方法结束以后,A方法出栈,Main出栈。
栈帧中主要保存3 类数据:
- 本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
- 栈操作(Operand Stack):记录出栈、入栈的操作;
- 栈帧数据(Frame Data):包括类文件、方法等等
一个方法被调用了,也就意味着在栈中多一个栈帧。栈帧中有什么信息(这个方法的参数,返回值,方法中的变量,以及这个方法属于哪个类,这个方法是被谁调用的。)
  栈是线程私有的,不需要垃圾回收。
  方法被调用,进入栈内存,但是一旦调用多了,栈内存满了,内存溢出(StackOverflowError)。
  一般出现在递归调用时,没有编写跳出递归的条件,会出现栈内存溢出。
5、堆(Heap)
    详见下面
  

4) 堆(Heap)

堆是管存储。我们new出来的对象,都放在堆中。堆至关重要。

堆内存分为三部分:

  • Young Generation Space 新生区 Young/New
  • Tenure generation space 养老区 Old/ Tenure
  • Permanent Space 永久区 Perm

新生区:存放被new出来的对象的。当新生区中的对象越来越多,就会引发GC(垃圾回收),每一次回收都会有一些幸存者,如果一个对象幸存了15次(默认值),就可以进入养老区养老,当养老区的对象也越来越多,就引发大GC。

一个对象被new出来,放入我们新生区中的伊甸区(Eden),当我们的Eden区使用率达到一定比例之后,进行一次小GC(minor GC), 将我们的Eden和幸存区的From区一起GC 的,幸存的对象,放入我们的幸存区的To区。放入之后,将伊甸区和From区清空。这个时候原来的To区就变为From区。

所谓的From区就是有数据的区域,谁有数据谁是From区。

伊甸区:From: To = 8:1:1

循环往复,一个对象超过15次小GC,仍然存活,就进入老年代(养老区)。

养老区:

1、躲过15次Minor GC的普通对象

2、大对象 String[] arr = new String[500*1024*1024]

养老区满了之后,我们需要进行Full GC (大GC)

如果养老区进行了大GC之后,还是没有空间存储后面的对象,直接报内存溢出(java.lang.OutOfMemoryError: Java heap space)

如果报堆内存溢出,必定进行过Full GC 了。

OutOfMemoryError

如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:

  • Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
  • 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

永久区(元空间):

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。

100M 的jar 包 --> 加载 (永久区)--> 永久区中的对象是不会被回收的。

如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。

背会:

  • Jdk1.6及之前: 有永久代, 常量池1.6在方法区
  • Jdk1.7: 有永久代,但已经逐步“去永久代”,常量池1.7在堆
  • Jdk1.8及之后: 无永久代,常量池1.8在元空间
  • 我们可以通过 -Xms -Xmx 设置堆内存大小,一般设置的一样。
  • 64M --> 600M
  • 每一次GC 耗费时间,干脆,直接给到位 600M 600M

运行参数设置:-Xms1024m -Xmx1024m -XX:+PrintGCDetails

二、GC垃圾回收

自己补充:垃圾回收器都有哪些牌子?

Garbage Collection, 简称GC, 是分代垃圾收集算法。 频繁收集Yong区, 较少收集Old区, 基本不动Perm区。

垃圾回收算法:

1、复制算法

将区域中存活的对象,一个个复制到一个新的区域,然后格式化老区域。这种做法就是复制算法。

优点:内存排列整齐,效率比较高

缺点:需要浪费一半的内存空间

我们的新生代就使用了这个算法,这个算法比较适用于存活率不高的场景。

2、标记清除算法

标记和清除是两个动作:

先对我们的内存整体进行标记,标记哪些对象存活者。

优点: 内存使用率比较高,100M 就能用100M

缺点: 需要扫描两次,会产生碎片。

老年代使用的GC算法用到了一部分这个算法。老年代使用的是标记清除和标记整理两种算法的混合使用。

3、标记整理

也是分为两个步骤:标记操作(有清除的成分),整理操作。

优点:内存使用率很高,也没有碎片

缺点:耗时

难道就没有一种最优算法吗? 猜猜看

没有最优的方式,分代整理算法(不同的区域使用不同的算法)

三、堆外内存(了解)

1、概念

堆内存分为堆内和堆外

以上说的是堆内: 新生代+老年代+元空间,由JVM管理

堆外内存:由操作系统管理的那部分区域。

2、好处

1) 堆外的部分不需要垃圾回收

2) 可以直接写磁盘上,提高效率(IO)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值