文章目录
JVM 是 Java 实现 跨平台的基础,所有的Java 程序都基于JVM,那么JVM底层到底是如何实现的呢,Java目前已火了20多年了,下面我们就一起来看看 这个强大的 JVM!!!
以下是面试高频题
请你谈谈你对JVM的理解?Java8虚拟机和之前的变化更新?
JVM(Java Virtual Machine):虚拟机 ,源文件.java在虚拟机中通过编译器编译成字节码文件.class,是整个java实现跨平台的最核心的部分
Java 8 虚拟机 撤销了 永久代,引入了 元空间的概念。
在HotSpot虚拟机中,jkd1.6时,设计团队把方法区设计为永久代,这样GC工作区域就可以扩展至方法区。这种策略可以避免为方法区单独设计垃圾回收机制,但是坏处就是,方法区的回收条件十分苛刻,而且回收效果也不好。
到了最新的Java1.8 ,撤销了永久代,改为了元空间。
元空间的规则:
元空间中类及其相关的元数据和类加载器生命周期一致,每个类加载器有专门的存储空间,不会单独回收某个类,位置也是固定的,但是当类加载器不再存活时会把它相关的空间全部移除。
什么是OOM?什么是栈溢出StackOverflowError?怎么分析?
OOM(OutOfMemoryError):内存溢出,原因是发生了某种原因 导致程序使用了大量的jar 和 class,使Java虚拟机的内存空间不足,与Permanent Generation space有关。
解决方案:
StackOverflowError: 栈溢出,当栈深度超过虚拟机分配给线程的栈大小时就会出现此Error。
注意: 递归的错误,才出现Stack满的情况,而无限循环一般不会占用更多的内存或者具体的Stack,只是占cpu而已,所以不会抛此错误。
分析:抓取内存快照,分析Dump文件。
JVM常用的调优参数有哪些?
内存快照如何抓取,怎么分析dump文件?
开启内存快照,当出现OOM时,会自动将dump文件放到改路径下
-XX:+HeapDumpOnOutOfMemoryError
# 把内存快照放到指定路径下
-XX:HeapDumpPath=/usr/local/app/oom
最重要的是要打印出来GC日志,GC日志可以配合你用jstat工具分析GC频率和性能的时候用,jstat可以分析出来GC的频率, 但是对每次具体的GC情况,可以结合GC日志来看
谈谈JVM中类加载器你的认识?
类加载器(ClassLoader) 是Java语言的一项创新,也是Java流行的一个重要原因。在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取定义此类的二进制字节流,完成这个动作的代码块就是类加载器。这一动作是放在Java虚拟机外部去实现的,以便让应用程序自己决定如何获取所需的类。
类加载器最重要的就是双亲委派模型,在下方会有说明。
JVM在JRE中



/*
双亲委派机制
1.类加载器收到类加载的请求 Application(应用加载)
2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到根加载器
3.根加载器检查是否能够加载当前这个类,能加载就结束,不能加载就抛出异常,通知子加载器进行加载
4. 重复步骤3. 直至完成加载
如果都没找到,则会抛出ClassNotFound!
null:java调用不到的加载器,是由于底层是由C++写的,调用的C++的本地栈方法,所以是null
*/
加载顺序
package java.lang;
public class String {
//双亲委派机制:安全,一层一层网上找,上面有就使用上面的,没有就从根部一层层外外找,直至找到为止
//1. APP(应用加载) ---> EXC(扩展加载) ---> BOOT(根加载,最终执行)
//BOOT没有 --> EXC 再没有 ---> APP找到!
@Override
public String toString() {
return "Hello World!!!";
}
public static void main(String[] args) {
String s = new String();
System.out.println(s.toString());
}
}
我们新建了一个java.lang.String类,当加载时会报错,为什么呢,是因为双亲委派机制,直接去调用了ROOT下的String 类
沙箱安全机制
了解即可
native是一个计算机函数,一个Native Method就是一个Java调用非Java代码的接口。方法的实现由非Java语言实现,比如C或C++。
我们知道,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。
如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用java.system.loadLibrary()实现的。
最后需要提示的是,使用本地方法是有开销的,它丧失了java的很多好处。如果别无选择,我们可以选择使用本地方法。
Thread类就调用了本地方法启动线程
//navive:凡是带了native关键字的方法,说明java的作用范围达不到了,会去调用C语言的库
//会进入本地方法栈
//Java诞生的时候 C、C++横扫天下,Java想要立足,就必须要有 调用C、C++的程序
//JNI作用:扩展Java程序的使用,融合不同的编程语言为Java所用,C/C++
//它在内存区域开辟了一块空间为本地方法栈(Native Method Stack) 来登记需要执行的本地方法
//在最终执行的时候,通过JNI加载本地方法库中的方法
private native void start0();
//调用其它语言的接口,http、Socket、WebService
掌握即可,企业级应用中较为少见!
PC寄存器
程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也指向即将要指向的指代代码),在执行引擎读取下一条命令,是一个非常小的内存空间,几乎可以忽略不计
方法区
Methad Area 方法区
方法区就是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在于堆内存中,和方法区无关
栈
栈是一种数据结构
程序=数据结构+算法
栈:先进后出,桶
队列:先进先出(FIFO),First Input First Output
喝多了吐就是栈,吃多了拉就是队列
一个简单的执行流程
public class Test {
public static void main(String[] args) {
new Test().test();
}
public void test(){
}
}
在内存图中的结构如下

public class Test {
public static void main(String[] args) {
new Test().test();
}
public void test(){
a()
}
public void a(){
test();
}
}
这样会无线堆积栈,直至栈内存溢出

这种错误是非常严重的,例如递归时我们经常见这样的错误,一旦发生,很难解决,避免程序OOM
对象在内存中的创建过程
public class Person {
private Integer noid;
private String name;
private Integer age;
public void info() {
System.out.println("学生的姓名:" + name);
System.out.println("学生的年龄:" + age);
System.out.println("学生的noid:" + noid);
}
public static void main(String[] args) {
//实例化对象
Person person = new Person();
//给属性赋值
person.name = "小智";
person.age = 20;
person.noid = 1;
//调用方法
person.info();
}
}
这段代码有3个成员变量,一个成员方法,我们为其赋值
内存结构图
在java内存中,创建对象有三个区域,栈(Stack)、堆(Heap)、方法区(Method Area)

将类信息和成员方法加载至方法区,将成员属性加载至堆

main函数进入栈区,并定义一个Person类型的引用指向Person类的实例,在堆区创建Person对象的实例

接下来赋值操作,现在栈区找到对象的引用,然后根据引用去堆区赋值

随后调用info方法,先找到栈区对象的引用,然后根据指向去堆区找到实例,再去方法去调用方法

最后执行方法,方法执行完毕后,方法被弹出,也叫出栈,最后main函数被弹出

至此,创建对象在内存中的过程完毕
堆
Heap,一个JVM只有一个堆内存大小,堆内存的大小是可以调节的,
类加载器读取了类文件后,一般会将什么东西放到堆中?方法,全局变量,保存我们引用的真实对象
堆内存还要细分3个区域

GC垃圾回收主要在伊甸园区和养老区
假设内存满了,就会爆OOM错误,堆内存不够
import java.util.Random;
public class Test {
public static void main(String[] args) {
String str = "";
while (true) {
str += str + new Random().nextInt(666666666)+new Random().nextInt(99999999);
}
}
}

在JDK8之后,永久存储区改为元空间
以下三种JVM
JRockit我们使用的是HotSpot
新生区
老年区
老年区,经历重重GC回收还没死亡的会进入老年区,进入老年区的对象少之又少

真理:经过研究,有99%的对象都是临时对象,都在伊甸园区,用完即失。
永久区
这个区域是常驻内存的,用来存放JDK自身携带的Class对象,Interface存放元数据,存储的是Java运行时的一些环境或类信息,这个区域不被GC垃圾回收,在关闭虚拟机的时候就会释放这个区域的内存。
一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态的生成反射类,不断的被加载,直到内存满,就会出现OOM
去永久代 常量池在堆中
逻辑上存在,物理上不存在
在一个项目中,突然出现了OOM故障,那么该如何排除,研究为什么出错~
MAT、Jprofiler作用:
以上就是【Bug 终结者】对 【高级篇】Java 进阶之JVM实战 的简单介绍,JVM 是Java 进阶必备, 在项目开发中,熟练的掌握了JVM 那就会感受到极其好的感受,JVM调优,提高程序QPS,吞吐量必备,同时 也是 Java 高级面试高频,可见,掌握并灵活运用JVM就可以说是达到了高级的水平!
如果这篇【文章】有帮助到你,希望可以给【Bug 终结者】点个赞👍,创作不易,如果有对【后端技术】、【前端领域】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.
Java的Collections.unmodifiableList和Collections.unmodifiableMap在Ruby标准API中是否有等价物? 最佳答案 使用freeze应用程序接口(interface):Preventsfurthermodificationstoobj.ARuntimeErrorwillberaisedifmodificationisattempted.Thereisnowaytounfreezeafrozenobject.SeealsoObject#frozen?.Thismethodretur