1.JVM体系结构

JVM的位置

JVM体系结构

2.类加载器

双亲委派机制

package java.lang;

/**
* 测试自定义java.lang.String类能否运行成功
* 体会双亲委派机制
*
* 类加载器逐级向上检查:app->ext->boot
* 发现boot类加载器中也有String类,但是没有main方法,于是报错
* app:应用程序加载器
* ext:扩展类加载器
* boot:启动类(根)加载器
*
* 检查什么?每一级类加载器能够加载的类是固定的,不能越级加载。
* boot能加载的类,app,ext就不能加载;同理,exit能加载的,app就不能加载。
* 一个形象的比喻:类,app,ext,boot分别对应平民,村长,乡长,县长。
* 村长会向乡长汇报,乡长向县长汇报。如果这个案子很特殊,应该有县长来处理,那么村长和乡长当然不能管了。
*
* 通过验证“hello”是否会输出,可以知道:先加载类,再去执行main方法。
* 类是方法的载体,包括main方法,想要用方法,就得先加载类
*/
public class String {
public static void main(String[] args) {
System.out.println("hello"); //这一行会输出吗?
String s = "";
System.out.println(s.getClass().getClassLoader());
}
}

运行结果:

错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application
public class Cat {
/**
* 测试自定义普通类使用哪个类加载器
*
* 从app到boot检查是否有Cat类,发现ext和boot中都没有Cat类
* 所有直接还是用app加载器
*/
public static void main(String[] args) {
System.out.println("Hello");
System.out.println(Cat.class.getClassLoader()); //运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2
}
}

3.沙箱安全机制

4.native

native是Java的一个关键字,使用它可以调用本地方法,访问本地资源。

Thread类中的一个用法:

private native void start0();

执行到start0()时,start0()进入本地方法栈,然后调用本地方法接口JNI(Java Native Interface),然后调用本地方法库。

5.方法区

存哪些东西

  • static变量
  • final变量
  • Class对象
  • 常量池

实例变量存放在堆中。

面试题:一个实例的创建过程?

  1. 类加载,Class对象存放在方法区中。

    1. 父类静态成员
    2. 子类静态成员
    3. 父类代码块
    4. 父类构造器
    5. 子类代码块
    6. 子类构造器
  2. 实例的声明 Object obj,存放在栈中。
  3. 实例的创建 new Object(),存放在堆中。
  4. 将对象实例的地址赋给对象的引用(栈中的变量名指向堆中具体的对象)。obj = new Object();
  5. 对对象的属性赋值。
  6. 调用方法。

6.栈

特点:先进后出,后进先出

为什么main方法先执行,后结束?

进栈顺序:main(),test1(),test2()

出栈顺序:test2(),test1(),main()

为什么递归会引起栈溢出?

当调用递归出现死循环的情况时,栈溢出也就出现了。

测试代码:

package stack_;

public class TestStack {
public static void main(String[] args) {
test1();
}
static void test1(){
test2();
} static void test2(){
test1();
}
}

运行结果:

Exception in thread "main" java.lang.StackOverflowError
at stack_.TestStack.test2(TestStack.java:12)
at stack_.TestStack.test1(TestStack.java:8)
……
此处省略1000多行(马德,typora软件都干卡死了)
……
at stack_.TestStack.test2(TestStack.java:12)
at stack_.TestStack.test1(TestStack.java:8) Process finished with exit code 1

7.堆

堆:heap

堆中的三个区域

  • 新生区

    • 伊甸园
    • 幸存区0
    • 幸存区1
  • 养老区
  • 永久区

新生区

伊甸园:类的创建,应用,甚至消亡。

伊甸园满了之后,触发GC,有一部分被销毁,有一部分进入幸存区。幸存区0,1又会发生数据交换。

老年区

新生区满了之后,触发Full GC,进入老年区。

老年区和新生区都满了,就会发生OOM。

一般不会出现OOM,因为99%的数据都是临时的,用完就不再使用,在伊甸园或幸存区就被回收了。

永久区

JDK1.6及以前:永久代,常量池位于方法区

JDK1.7:永久代,常量池位于堆

JDK1.8及以后,永久区更名为:元空间,常量池位于元空间

内存调优

//虚拟机需要的最大内存
long max = Runtime.getRuntime().maxMemory();
//虚拟机初始化时的总内存
long original = Runtime.getRuntime().totalMemory(); System.out.println("max:" + max + "Byte," + max / 1024 / 1024 + "MB");
System.out.println("original:" + original + "Byte," + original / 1024 / 1024 + "MB");
/**
* 调优之前:
* max / 电脑内存 ≈ 1 / 4
* original / 电脑内存 ≈ 1 / 64 *内存调优:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
*/

运行结果:

max:1029177344Byte,981MB
original:1029177344Byte,981MB
Heap
PSYoungGen total 305664K, used 20971K
eden space 262144K, 8% used
from space 43520K, 0% used
to space 43520K, 0% used
ParOldGen total 699392K, used 0K
object space 699392K, 0% used
Metaspace used 3282K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 356K, capacity 388K, committed 512K, reserved 1048576K Process finished with exit code 0

(305664K+699392K)/ 1024 = 981M

元空间的内存逻辑上存在,物理上不存在。

OOM 内存溢出

案例演示

package heap_;

import java.util.Random;

/**
* 测试堆内存溢出
*/
public class TestOOM {
public static void main(String[] args) {
String s = "hello";
while (true){
s += s + s + new Random().nextInt(999999999);
}
}
}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at heap_.TestOOM.main(TestOOM.java:9) Process finished with exit code 1

Java中的字符串可以不停的增加长度,但是JVM中的堆内存空间是有限的。

对虚拟机参数进行调整(-Xms8m -Xmx8m -XX:+PrintGCDetails),并观察运行结果。

可以看到很快就会运行结束,并报OOM错误。

出现OOM,如何解决?

  • 内存调大一点
  • 如果还是有问题,就要研究代码,是否有bug

使用Jprofiler分析内存

Jprofiler安装教程:https://www.cnblogs.com/zhangxl1016/articles/16220183.html

测试代码:

package heap_;

import java.util.ArrayList;

public class TestDump {
byte[] arr = new byte[1024 * 1024]; //共1MB的空间 public static void main(String[] args) {
ArrayList<TestDump> list = new ArrayList<>();
int count = 0;
try {
while (true) {
list.add(new TestDump());
count++;
}
}
//OOM要用Error来捕获
catch (Error error) {
System.out.println("count=" + count);
error.printStackTrace();
} }
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3996.hprof ...
Heap dump file created [7778880 bytes in 0.024 secs]
count=6
java.lang.OutOfMemoryError: Java heap space
at heap_.TestDump.<init>(TestDump.java:6)
at heap_.TestDump.main(TestDump.java:13) Process finished with exit code 0

并在当前项目的根目录下生成了Jprofiler文件:

双击打开:

可以看到最上边的列表项(ArrayList)占用的内存是最高的,说明是它出了问题。

那么具体是代码中的哪一行有问题呢?

因为这个案例只有main方法一个线程,所以直接点main,然后在下方可以看到是源代码第13行出了问题。

为什么到count=6时就报错了呢?因为在第13行每加一个对象,就会增加1MB的空间,我们分配的最大空间是8MB,所以加到6的时候,就会发生内存溢出。

记得删除生成的Jprofiler文件,因为比较占用空间。

虚拟机参数

-Xms 设置初始化内存大小 默认1/64

-Xmx 设置最大分配内存 默认1/4

-XX:PrintGCDetails 打印GC垃圾回收信息

-XX:HeapDumpOnOutOfMemoryError 转储OOM异常信息

上一个案例设置为:-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

垃圾回收

哪些东西是垃圾?

不需要的,而且占了空间的对象。

在哪里回收?

堆

为什么不在栈回收?因为栈里没有垃圾,栈不存对象,只存储变量的引用和方法。

举个例子:假设堆中存的是具体的人,那么栈中存的是人的姓名。人去世之后,这个具体的对象依然存在堆中,而且占用空间,所以它需要被回收。栈里存的名字,用完了就自动出栈了,所以栈中不存在垃圾,亦无需回收。

如何回收?

引用计数法

每一个对象都有一个计数器,某个对象被使用一次,相应的计数器就会加1。垃圾回收时,计数器为0的对象被回收。

缺点:当对象很多时,计数器也会占用大量的资源。

复制算法

主要针对新生区

假设当前伊甸园有两个对象:o1,o2,GC之后,o1被销毁,o2进入幸存区。幸存区有两个,要去哪个呢?哪个是空的,就去哪个。另外一个幸存区中,如果有对象,也会进入to区。然后当前的to区又变成from区,from区又变成to区。一定要保证有一个幸存区是空的,这个区就是to区。

”谁空谁是to“

一个对象在新生区经历15次(默认次数)还没有消亡,那么它会进入老年区,类似于久经沙场的老兵,活到最后就可以养老了。

这里涉及到一个JVM参数:-XX:MaxTenuringThreshold=15,默认值是15,如果这个值调到很大,那么新生区的对象会很难进入到老年区中。

  • 优点:没有内存碎片。
  • 缺点:浪费空间。
    • 总是要保证to区是空的。
    • 假设对象100%存活,to区就要面临无法容纳所有对象的情况。to区同时要面临伊甸园和form区两个方向的对象。
  • 最佳使用场景:对象存活度较低的区域---新生区。
标记压缩清除算法

第一次扫描:标记存活的对象,没被标记的就是不需要的

第二次扫描:清除没用的对象,会产生碎片

最后一次扫描:被标记的对象向一侧移动,另一侧就是被清除掉的

优点:不会增加额外的空间

缺点:扫描会浪费时间,会有内存碎片产生

优化:先进行几次标记清除,最后再统一压缩

算法比较
  1. 内存效率:复制算法>标记清除>标记压缩(时间复杂度)

    • 复制是一个动作,清除是两个动作,压缩有三个动作
  2. 内存整齐度:复制算法=标记压缩>标记清除

    • 复制和压缩都没有内存碎片
  3. 内存利用率:标记清除=标记压缩>复制

    • 清除和压缩都在原有空间中操作,复制算法总是要保证to区是空的

有没有最优算法呢?

没有,只有最合适的:分代收集算法

新生代:存活率低,适合复制算法。不用担心to区的空间不够。

老年代:区域大,存活率高,适合标记清除+标记压缩混合实现。碎片不是很多的时候用标记清除,碎片积累到一定程度,就需要压缩,这其中就要涉及到内存调优。

学习JVM---入门的更多相关文章

  1. 学习JVM是如何从入门到放弃的?

    前言 只有光头才能变强 JVM在准备面试的时候就有看了,一直没时间写笔记.现在到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书. 学习JVM的目的也很简单: 能够知道JVM是什么,为我们干 ...

  2. Java工程师学习指南 入门篇

    Java工程师学习指南 入门篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我之前写的文章都 ...

  3. Elasticsearch学习记录(入门篇)

    Elasticsearch学习记录(入门篇) 1. Elasticsearch的请求与结果 请求结构 curl -X<VERB> '<PROTOCOL>://<HOST& ...

  4. Python学习--01入门

    Python学习--01入门 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.和PHP一样,它是后端开发语言. 如果有C语言.PHP语言.JAVA语言等其中一种语言的基础,学习Py ...

  5. [IT学习]sql 入门及实例

    sql 是一种数据库查询语言,可以让你很快的查询到数据.其实一般情况下,你也可以采用excel来查询数据库数据. 但是人们通常认为sql会更加灵活和方便一些. sql学习的入门网站: http://w ...

  6. PHP学习笔记 - 入门篇(5)

    PHP学习笔记 - 入门篇(5) 语言结构语句 顺序结构 eg: <?php $shoesPrice = 49; //鞋子单价 $shoesNum = 1; //鞋子数量 $shoesMoney ...

  7. PHP学习笔记 - 入门篇(4)

    PHP学习笔记 - 入门篇(4) 什么是运算符 PHP运算符一般分为算术运算符.赋值运算符.比较运算符.三元运算符.逻辑运算符.字符串连接运算符.错误控制运算符. PHP中的算术运算符 算术运算符主要 ...

  8. PHP学习笔记 - 入门篇(3)

    PHP学习笔记 - 入门篇(3) 常量 什么是常量 什么是常量?常量可以理解为值不变的量(如圆周率):或者是常量值被定义后,在脚本的其他任何地方都不可以被改变.PHP中的常量分为自定义常量和系统常量 ...

  9. PHP学习笔记--入门篇

    PHP学习笔记--入门篇 一.Echo语句 1.格式 echo是PHP中的输出语句,可以把字符串输出(字符串用双引号括起来) 如下代码 <?php echo "Hello world! ...

  10. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

随机推荐

  1. BZ全景可视化编辑器 (KRPano可视化编辑器, 无需编写任何代码制作全景漫游)

    软件简介 BZ全景编辑器是一款KRPano全景可视化编辑工具,下载安装即可使用,无需拥有任何KRPano代码基础,便可以制作生成精美的全景漫游作品. 官方网站: 点击进入官方网站 最新版软件下载地址: ...

  2. 聊聊数据库事务内嵌TCP连接

    最近再看项目代码,发现很多的service里面,喜欢在事务内部再去调用HTTP请求,简单分析下此种方式的利弊与解决策略. 概述 在数据库内部嵌套TCP连接(一般是HTTP调用或是RPC远程调用). @ ...

  3. Tcp/Ip协议 A类B类C类D类 地址

    TCP(传输控制协议):负责和远程主机连接  Ip(网际协议):负责寻址,使报文发送到其该在的地方 Ip地址:是TCP/IP的网络层用以标识网络中主机的逻辑地址,可以唯一标识Interent中的一台主 ...

  4. 从内核世界透视 mmap 内存映射的本质(原理篇)

    本文基于内核 5.4 版本源码讨论 之前有不少读者给笔者留言,希望笔者写一篇文章介绍下 mmap 内存映射相关的知识体系,之所以迟迟没有动笔,是因为 mmap 这个系统调用看上去简单,实际上并不简单, ...

  5. 浅谈Code Review

    1. 什么是Code Review Code Review(CR)即代码评审,又名代码走查,是指对软件开发过程中编写的代码进行检查和评估的一种实践.它通常由其他团队成员.同事或专门的质量保证团队成员来 ...

  6. PHPStudy hosts文件可能不存在或被阻止打开及同步hosts失败问题

    在使用PHPStudy建站包时,有时会遇到同步hosts失败的问题,可能是因为hosts文件不存在或被阻止打开.这个问题通常可以通过以下几个步骤解决: 步骤一:检查hosts文件是否存在 首先,我们需 ...

  7. DDMS查看Threads情况

    有时候程序运行出现死锁或者信号量卡死是很纠结的问题,单看代码很难分析定位问题,这时候可以借助DDMS来查看threads的运行情况,一目了然. 手机连接上USB,确保adb连通,然后启动Eclipse ...

  8. 轻松掌握组件启动之MongoDB(番外篇):高可用复制集架构环境搭建-mtools

    引言 在前两章节中,我们详细讲解了如何手动配置启动MongoDB.然而,现在有许多不同的工具可以帮助我们更方便地启动和创建MongoDB数据库.因此,今天我将介绍一个名为mtools的开源项目,它可以 ...

  9. 深入探讨I/O模型:Java中的阻塞和非阻塞和其他高级IO应用

    引言 I/O(Input/Output)模型是计算机科学中的一个关键概念,它涉及到如何进行输入和输出操作,而这在计算机应用中是不可或缺的一部分.在不同的应用场景下,选择正确的I/O模型是至关重要的,因 ...

  10. Kafka 在分布式系统中的 7 大应用场景

    Kafka 介绍 Kafka 是一个开源的分布式流式平台,它可以处理大量的实时数据,并提供高吞吐量,低延迟,高可靠性和高可扩展性.Kafka 的核心组件包括生产者(Producer),消费者(Cons ...