首页
归档
留言
友链
广告合作
壁纸
更多
美女主播
Search
1
博瑞GE车机升级/降级
5,610 阅读
2
Mac打印机设置黑白打印
4,943 阅读
3
修改elementUI中el-table树形结构图标
4,895 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,674 阅读
5
intelliJ Idea 2022.2.X破解
4,357 阅读
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Vue
Spring Cloud
Mac
MyBatis
WordPress
MacOS
asp.net
Element UI
Nacos
.Net
Spring Cloud Alibaba
MySQL
Mybatis-Plus
Typecho
jQuery
Java Script
IntelliJ IDEA
微信小程序
Laughing
累计撰写
627
篇文章
累计收到
1,421
条评论
首页
栏目
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
友链
广告合作
壁纸
美女主播
搜索到
627
篇与
的结果
2025-05-12
IDEA、Datagrip 2025.1 最新破解版安装教程(至2099年)
截止到本文发文时,IntelliJ IDEA最新版本是2025.1.1,Datagrip的最新版本是2025.1.2,本文介绍的破解教程都适用。安装最新版本软件直接在官网下载最新版本的(也可以参考我们上文介绍的版本)IntelliJ IDEA 或者Datagrip安装即可。下载破解文件通过以下百度网盘地址,下载最新的破解文件。{cloud title="jetbra.zip" type="bd" url="https://pan.baidu.com/s/1JU6J-e-Zh8WGIrfOvxksFg?pwd=yndh" password="yndh"/}下载完成后,解压到一个不包含空格及中文的路径下。里面有三个文件夹及两个文本文件:win2021-2015: 存放的是 Windows 系统对应的破解脚本;mac2021-2015: 苹果 Mac 系统对应的破解脚本;linux2021-2015: Linux 系统对应的破解脚本;Datagrip激活码.txt:对应Datagrip的激活码,破解完成后适用IDEA激活码.txt:对应IDEA的激活码,破解完成后适用执行激活Windows系统为例,进入到/win2020-2025文件夹,双击install-current-user.vbs 脚本,表示为当前系统用户执行破解。然后,点击弹框中的确定按钮,等待一会,弹出OK弹窗代表激活成功。# 激活激活成功后,打开IDEA或Datagrip。在激活页面,选择激活码,然后按照我们压缩包内提供的激活码选择激活至此,就完成了激活过程,有效期至2099年12月31日
2025年05月12日
10 阅读
0 评论
0 点赞
2025-05-09
LogicFlow节点实现呼吸闪烁效果
LogicFlow是滴滴开源的流程图编辑框架,提供了一系列流程图编辑和交互功能。我们这里主要实现某个节点呼吸闪烁效果。如下图蓝色的节点,我们假设当前节点是活动节点。一、定义样式我们借助animation属性实现动画效果。.breathing-border { animation: breathing 2s infinite; } @keyframes breathing { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }二、加载样式上面定义了样式之后,我们要做的就是将样式加载到我们的节点上。因为我这里使用的矩形节点,所以这里继承RectNode实现自己的规则。//自定义View class RectRadiusView extends RectNode { getShape() { // 获取XxxNodeModel中定义的形状属性 const { model } =this.props; const { x, y, width, height, radius, properties } =model; // 获取XxxNodeModel中定义的样式属性 const style=model.getNodeStyle(); const isActiveStep=properties.isActiveStep; return h('g', {} , [ h('rect', { ...style, x: x - width / 2, y: y - height / 2, width, height, rx: radius, ry: radius, className: isActiveStep ? 'breathing-border' : '', }), ]); } } // 自定义model class RectRadiusModel extends RectNodeModel { // 样式属性 getNodeStyle() { const style=super.getNodeStyle() const { fill } =this.properties style.fill=fill style.strokeWidth=0 return style } // 形状属性 initNodeData(data) { super.initNodeData(data) this.width=150 this.height=50 this.radius=25 } }扩展完自己的节点后,注册上lf.register({ type: "rectRadiusNode", view: RectRadiusView, model: RectRadiusModel })至此,就实现了我们节点的一个呼吸闪烁的效果。
2025年05月09日
8 阅读
0 评论
0 点赞
2025-04-24
Java线程组和线程优先级
一、线程组Java中ThreadGroup表示线程组,每个线程比如属于一个线程组,创建线程时,如果没有指定线程组,那么新创建的线程默认属于其父线程所在线程组。public class ThreadDemo { public static void main(String[] args) { Thread testThread = new Thread(() -> { System.out.println("测试线程组:" + Thread.currentThread().getThreadGroup().getName()); System.out.println("测试线程名:" + Thread.currentThread().getName()); }); testThread.start(); System.out.println("main线程名:" + Thread.currentThread().getName()); } }线程组管理其中的线程,线程组是一个标准的树状结构,这样涉及的原因是防止“上级”线程被“下级”线程引用而无法进行有效的GC回收。二、线程优先级Java中线程优先级是支持指定的,范围是1-10,但并不是所有操作系统都支持10级优先级,,Java只给操作系统一个优先级的参考量,线程在操作系统中最终的优先级还是有操作系统决定的。线程默认优先级是5,线程的执行顺序由调度程序来决定,通常情况下,高优先级的线程会比低优先级的线程有更高的机率得到执行。import java.util.stream.IntStream; public class ThreadDemo { public static void main(String[] args) { IntStream.range(1, 10).forEach(i -> { Thread thread = new Thread(() -> { System.out.println(String.format("当前线程名称%s,优先级%s", Thread.currentThread().getName(), Thread.currentThread().getPriority())); }); thread.setPriority(i); thread.start(); }); } }某次的输出结果当前线程名称Thread-5,优先级6 当前线程名称Thread-0,优先级1 当前线程名称Thread-1,优先级2 当前线程名称Thread-4,优先级5 当前线程名称Thread-7,优先级8 当前线程名称Thread-2,优先级3 当前线程名称Thread-8,优先级9 当前线程名称Thread-6,优先级7 当前线程名称Thread-3,优先级4Java提供⼀个线程调度器来监视和控制处于RUNNABLE状态的线程。线程的调度策略采⽤抢占式,优先级⾼的线程⽐优先级低的线程会有更⼤的⼏率优先执⾏。在优先级相同的情况下,按照“先到先得”的原则。每个Java程序都有⼀个默认的主线程,就是通过JVM启动的第⼀个线程main线程。线程组与线程都有优先级,当线程组内的线程优先级高于线程组的优先级时,系统会自动设置线程的优先级为线程组的优先级。public class ThreadDemo { static int i = 0; public static void main(String[] args) throws Exception { ThreadGroup threadGroup = new ThreadGroup("TestThreadGroup"); threadGroup.setMaxPriority(4); Thread thread = new Thread(threadGroup, () -> { System.out.println("测试线程优先级与线程组优先级"); }); thread.setPriority(6); System.out.println("线程实际优先级:" + thread.getPriority()); } }输出结果线程实际优先级:4三、守护线程如果某线程是守护线程,那如果所有的⾮守护线程结束,这个守护线程也会⾃动结束。守护线程优先级较低。⼀个线程默认是⾮守护线程,可以通过setDaemon(boolean on)来设置一个线程是否是守护线程。import java.util.stream.IntStream; public class ThreadDemo { static int i = 0; public static void main(String[] args) throws Exception { Thread thread = new Thread(() -> { while (true) { System.out.println("我还活着" + ++i); } }); thread.start(); System.out.println("main方法执行完成"); } }输出结果:main方法执行完成 我还活着1 我还活着2 ......引入thread线程默认不是守护线程,所以即使main线程结束后,thread线程也会一直循环下去。我们稍作稍作修改,把thread线程设置成非守护线程。import java.util.stream.IntStream; public class ThreadDemo { static int i = 0; public static void main(String[] args) throws Exception { Thread thread = new Thread(() -> { while (true) { System.out.println("我还活着" + ++i); } }); thread.setDaemon(true); thread.start(); //让main线程暂停10ms,防止守护线程未执行main线程就结束的情况 thread.sleep(10); System.out.println("main方法执行完成"); } }某次输出结果我还活着1 我还活着2 我还活着3 我还活着4 我还活着5 我还活着6 我还活着7 我还活着8 我还活着9 我还活着10 我还活着11 我还活着12 我还活着13 我还活着14 我还活着15 我还活着16 我还活着17 我还活着18 我还活着19 我还活着20 我还活着21 我还活着22 我还活着23 我还活着24 我还活着25 我还活着26 我还活着27 我还活着28 我还活着29 我还活着30 我还活着31 我还活着32 我还活着33 我还活着34 我还活着35 我还活着36 我还活着37 我还活着38 我还活着39 我还活着40 我还活着41 我还活着42 我还活着43 我还活着44 我还活着45 我还活着46 我还活着47 我还活着48 我还活着49 我还活着50 我还活着51 我还活着52 我还活着53 我还活着54 我还活着55 我还活着56 我还活着57 我还活着58 我还活着59 我还活着60 我还活着61 我还活着62 我还活着63 我还活着64 我还活着65 我还活着66 我还活着67 我还活着68 我还活着69 我还活着70 我还活着71 我还活着72 我还活着73 我还活着74 我还活着75 我还活着76 我还活着77 我还活着78 我还活着79 我还活着80 我还活着81 我还活着82 我还活着83 我还活着84 我还活着85 我还活着86 我还活着87 我还活着88 我还活着89 我还活着90 我还活着91 我还活着92 我还活着93 我还活着94 我还活着95 我还活着96 我还活着97 我还活着98 我还活着99 我还活着100 我还活着101 我还活着102 我还活着103 我还活着104 我还活着105 我还活着106 我还活着107 我还活着108 我还活着109 我还活着110 我还活着111 我还活着112 我还活着113 我还活着114 我还活着115 我还活着116 我还活着117 我还活着118 我还活着119 main方法执行完成 我还活着120 我还活着121 我还活着122 我还活着123 我还活着124可以看到main线程执行完成后,守护线程会自动关闭。
2025年04月24日
17 阅读
0 评论
0 点赞
2025-04-24
Java Callable接口、Future接口及FutureTask类用法
通常来讲,我们使用Runnable接口或者Thread类来创建一个新线程,但是他们有一个缺点,就是run方法没有返回值,如果我们需要开启线程后获得一个返回值,就可以使用Callable接口配合Future类来实现。一、Callable接口Callable⼀般是配合线程池⼯具ExecutorService来使⽤。ExecutorService可以使⽤submit⽅法来让⼀个Callable接⼝执⾏,它会返回⼀个Future,我们后续的程序可以通过这个Future的get⽅法得到结果。可以看一下下面的Demoimport java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class FutureDemo implements Callable<String> { @Override public String call() throws Exception { return "Hello,World"; } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); FutureDemo futureDemo = new FutureDemo(); Future<String> tFuture = executorService.submit(futureDemo); try { System.out.println(tFuture.get()); } catch (Exception exception) { } } }运行后,控制台会输出Hello,World二、Future接口看一下Future接口的源代码public interface Future<V> { /** * 试图取消一个线程的执行,但是不一定能成功,因为任务可能已完成或已取消或其他因素不能取消 * * @param 是否允许打断正在执行的任务 * @return 是否取消成功 */ boolean cancel(boolean mayInterruptIfRunning); /** * 获取线程是否已取消 * * @return 线程完成前被取消返回true */ boolean isCancelled(); /** * 获取线程是否已完成 * * 如果线程异常、被终止或者取消,都会返回true * * @return 线程完成返回true */ boolean isDone(); /** * 获取线程返回值 * * @return 线程返回结果 */ V get() throws InterruptedException, ExecutionException; /** * 在给定时间内获取返回值,如果超时未获取到返回超时异常 * * @param 获取结果最大等待时间 * @param 时间计量单位 * @return 结果 */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }下面通过实际代码看一下Future接口的基本用法。修改上面代码如下import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class FutureDemo implements Callable<String> { @Override public String call() throws Exception { return "Hello,World"; } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); FutureDemo futureDemo = new FutureDemo(); Future<String> tFuture = executorService.submit(futureDemo); // 判断线程是否完成 System.out.println("线程是否完成:" + tFuture.isDone()); // 判断线程是否取消 System.out.println("线程是否取消:" + tFuture.isCancelled()); try { System.out.println(tFuture.get()); // 判断线程是否完成 System.out.println("线程是否完成:" + tFuture.isDone()); // 判断线程是否取消 System.out.println("线程是否取消:" + tFuture.isCancelled()); } catch (Exception exception) { } } } 输出结果线程是否完成:false 线程是否取消:false Hello,World 线程是否完成:true 线程是否取消:false可以看到,在获取到结果前,线程是未完成、未取消的,获取到结果后,线程正常结束,此时线程是已完成、未取消的。下面代码继续修改,我们在获取结果之前先取消,因为线程可能来不及取消已经完成,所以我们加个让call方法睡眠1s。import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.CancellationException; public class FutureDemo implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(1000); return "Hello,World"; } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); FutureDemo futureDemo = new FutureDemo(); Future<String> tFuture = executorService.submit(futureDemo); // 判断线程是否完成 System.out.println("线程是否完成:" + tFuture.isDone()); // 判断线程是否取消 System.out.println("线程是否取消:" + tFuture.isCancelled()); try { // 试图取消线程 boolean isCancelled = tFuture.cancel(true); System.out.println("任务是否取消成功:" + isCancelled); // 判断线程是否完成 System.out.println("线程是否完成:" + tFuture.isDone()); // 判断线程是否取消 System.out.println("线程是否取消:" + tFuture.isCancelled()); // get的时候需要判断线程状态 System.out.println(tFuture.get()); } catch (CancellationException cancellationException) { System.out.println("线程已经被取消"); } catch (Exception exception) { exception.printStackTrace(); } } } 输出结果线程是否完成:false 线程是否取消:false 任务是否取消成功:true 线程是否完成:true 线程是否取消:true 线程已经被取消可以看到,线程已经取消成功,线程取消成功后,如果还是调用get方法,会抛出CancellationException异常,并且线程取消成功后,isDone方法也会返回true,代表线程已经完成。最后一个方法,我们看一下增加get的重载方法。import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.CancellationException; public class FutureDemo implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(1000); return "Hello,World"; } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); FutureDemo futureDemo = new FutureDemo(); Future<String> tFuture = executorService.submit(futureDemo); try { // get方法如果0.5秒内未获取到结果就抛出超时异常 System.out.println(tFuture.get((long)0.5, TimeUnit.SECONDS)); } catch (TimeoutException timeoutException) { System.out.println("超过0.5s未获取到结果"); } catch (Exception exception) { exception.printStackTrace(); } } } 输出结果超过0.5s未获取到结果三、FutureTask类FutureTask是Java自带的Future接口的实现类。FutureTask是实现的RunnableFuture接⼝,⽽RunnableFuture接⼝同时继承了Runnable接⼝和`Future接⼝。import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.CancellationException; public class FutureDemo implements Callable<String> { @Override public String call() throws Exception { return "Hello,World"; } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); FutureDemo futureDemo = new FutureDemo(); FutureTask<String> futureTask = new FutureTask<>(futureDemo); executorService.submit(futureTask); try { System.out.println(futureTask.get()); }catch (Exception exception) { exception.printStackTrace(); } } } 输出结果:Hello,World文章摘抄自《深入浅出Java多线程.pdf》并对内容进行完善。
2025年04月24日
13 阅读
0 评论
0 点赞
2025-04-23
Java volatile关键字的用法
volatile是Java中的关键字,用于修饰变量,被volatile修饰的变量可以确保线程对这个变量的读写都是直接从主内存在中进行的。我们先来看下面一段代码public class Volatile { private boolean ready; public void setReady(boolean ready) { this.ready = ready; } public boolean getReady() { return this.ready; } public static void main(String[] args) throws Exception { Volatile volatile1 = new Volatile(); new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException exception) { exception.printStackTrace(); } volatile1.setReady(true); System.out.println("设置ready值为true"); }).start(); new Thread(() -> { while (!volatile1.getReady()) { } System.out.println("检测到ready变成" + volatile1.getReady()); }).start(); } }运行这段代码,我们查看控制台,可以看到虽然我们在第一个线程中将ready值设置成了true,但是第二个线程中,while仍然进入了一个死循环。这是因为在两个线程中,ready的值被隔离了,所以我们在第一个线程中修改的ready值在第二个线程中是获取不到的。这个时候,我们可以给ready变量加上volatile关键字进行修饰,修改后代码如下public class Volatile { private volatile boolean ready; public void setReady(boolean ready) { this.ready = ready; } public boolean getReady() { return this.ready; } public static void main(String[] args) throws Exception { Volatile volatile1 = new Volatile(); new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException exception) { exception.printStackTrace(); } volatile1.setReady(true); System.out.println("设置ready值为true"); }).start(); new Thread(() -> { while (!volatile1.getReady()) { } System.out.println("检测到ready变成" + volatile1.getReady()); }).start(); } }再次运行代码,可以发现第一个线程中的内容正常输出了,这就是volatile关键字的作用,一个线程中修改被volatile修饰的变量,另外一个变量能立即看到修改后的值。volatile关键字有两个作用:可见性及有序性。可见性当一个线程修改了被volatile修饰的变量的值,其他线程能够立刻看到修改后的值。这是因为被volatile修饰的变量不会被缓存到寄存器或其他处理器不可见的地方,因此保证了每次读取volatile变量都会从住内存中读取最新的值。有序性被volatile修饰的变量的读写具有一定的有序性,也就是禁止了指令重排序优化,这就意味着,在一个线程中,对被volatile修饰的变量的写操作一定发生在后续对这个变量的读操作之前。
2025年04月23日
15 阅读
0 评论
0 点赞
2025-04-21
Java Runnable接口的run()方法和start()方法
在Java 1.8及后续的版本中,我们有三种(也可以说是两种)方法开启线程。继承Thread类实现Runnable接口使用Java 8的函数式编程其中1和3的方式没有什么歧义,我们重点说一下Runnable接口。其实标题说Runnable接口的run()方法和start()方法是不准确的,严格来讲,应该是Runnable接口的run()方法和实现Runnable接口的实例的start()。先说一下结论,start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;而run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码,程序中只有主线程——这一个线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的。我们可以通过下面代码进行验证。public class Demo { static class Runner1 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 10; i++) { System.out.println("进入Runner1运行状态——————————" + i); } } } static class Runner2 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 10; i++) { System.out.println("进入Runner2运行状态==========" + i); } } } public static void main(String[] args) { Runner1 runner1 = new Runner1(); Runner2 runner2 = new Runner2(); runner1.run(); runner2.run(); // new Thread(runner1).start(); // new Thread(runner2).start(); } }看一下控制台输出内容,程序是先执行完runner1之后才执行的runner2方法,系统并没有给线程排队,相当于runner1和runner2是顺序执行的。我们修改上面的代码,改成start()方法执行public class Demo { static class Runner1 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 10; i++) { System.out.println("进入Runner1运行状态——————————" + i); } } } static class Runner2 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程 public void run() { for (int i = 0; i < 10; i++) { System.out.println("进入Runner2运行状态==========" + i); } } } public static void main(String[] args) { Runner1 runner1 = new Runner1(); Runner2 runner2 = new Runner2(); // runner1.run(); // runner2.run(); new Thread(runner1).start(); new Thread(runner2).start(); } }再次查看控制台可以看到runner1和runner2是交替执行的,也就证明了,此时正确的开启了线程。
2025年04月21日
9 阅读
0 评论
0 点赞
2025-04-21
JProfiler 14注册码
JProfiler是ej-technologies公司开发一款专业的Java性能分析工具,它可以帮助开发人员识别和解决Java应用程序中的性能瓶颈、内存泄漏和线程问题。它提供直观、强大的图形界面,支持多种操作系统,包括Windows、macOS和Linux。{message type="success" content="JProfiler 14注册码"/}隐藏内容,请前往内页查看详情
2025年04月21日
12 阅读
0 评论
0 点赞
2025-04-20
钢琴键花洒顶喷嗡嗡异响问题解决
2025年刚过完年的时候,把家里换成了智能马桶及还有钢琴键花洒,当时安装的时候我没在家,晚上到家洗澡的时候,发现顶喷嗡嗡的响,当时嫌麻烦,也没让卖家过来修。最近闲着没事,想着这花洒一直响着也不是个事,特别是晚上洗澡的时候,次卧的声音还是挺大的,于是上网搜索了一下,发现别人也有遇到过的,找到了几个解决方案,最终解决了问题。先说一下我这边的问题只有顶喷响,其他的都不响。当时把顶喷拆下来只有,异响仍然存在,所以可以排除是花洒头导致的问题。把热水器热水出口关闭之后,顶喷只出凉水,也不会响解决问题上网搜索了一下,有人说是高层水压问题,有人说是总出水口的问题,也有人说是止逆阀的问题,因为水压问题或者出水口问题,咱们自己也无法解决,所以我首先按照止逆阀的思路来处理了,结果拆掉止逆阀之后,异响真的没有了。其实,解决也很简单。先把顶喷逆时针旋转拧下来找个尖嘴钳把止逆阀拧下来然后重新把顶喷顺时针安装上,再次开水,异响就这么解决了。{message type="error" content="不清楚止逆阀有什么作用"/}就这,找啄木鸟,不得收你300快钱~~~
2025年04月20日
8 阅读
0 评论
0 点赞
2025-04-20
JVisualVM:多合一故障处理工具
VisualVM是功能最强大的虚拟机运行监视和故障处理程序之一,曾经在很长一段时间内是Oracle官方助理发展的虚拟机故障处理工具。VisualVM的性能分析功能,比起JProfiler等专业收费工具也不遑多让,相比于第三方的功能,VisualVM还有一个很大的优点:不需要被监视的程序基于特殊的Agent去运行,因此它的通用性很强,对应用程序实际性能影响也比较小,使得它可以直接应用于生产环境。在这个注重颜值的时代,相较于JProfiler,可能界面比较丑陋是最大的缺点了吧。在强大的插件加持下,VisualVM可以实现包括但不限于以下的功能:显示虚拟机进程以及进程的配置、环境信息显示应用程序的处理器、垃圾收集、堆、方法区及线程信息dump以及分析堆转储快照方法级的应用程序性能分析,找出被调用最多、运行时间最长的方法壹、安装插件VisualVM插件可以直接在线安装,依次点击【tools】- 【plugins】勾选需要安装的插件,点击【install】即可进行安装。贰、生成、浏览堆转储快照生成和浏览堆转储快照可能是日常开发过程中分析性能最常见的操作,下面介绍下在visualVM中如何生成或者分析堆转储快照。一、生成堆转储快照我们运行一个程序后,在visualVM左侧Local下,会自动加载对应的进程信息。比如我这里的JHSDB_TestCae,我们下面都以这个为例进行说明。可以通过以下两种方式之一生成堆转储快照选择应用程序,点击右键,然后选择【Heap Dump】双击应用程序,在打开的页面中,选择【Monitor】,然后选择【Heap Dump】快照存储后会自动打开。二、打开堆转储快照如果是别人发过来的快照文件,可以点击左上角的文件图标打开。Overview页签显示的是一些基本信息,包括pid、Java版本、虚拟机参数、环境参数等信息。Monitor页签是图标信息显示的CPU、堆、元空间、类及线程等信息。Threads页签显示的是线程信息Sampler页签以一定的时间间隔对CPU、内存进行采样,可检查出占用CPU时间较多或占用内存空间较大的线程,有助于性能调优。对CPU采样时,该页提供CPU样例(CPU samples)和线程CPU时间(Thread CPU time)两个子页签,前者可用于分析调用链上的方法耗时,后者可用于比较线程CPU耗时。profiler是性能分析页签,因为性能分析对程序运行性能影响较大,生产环境不建议使用。叁、常见插件介绍上面所过,VisualVM是支持插件的,下面我们介绍几款常用的插件。一、BTrace动态日志跟踪BTrace允许在不中断目标程序的前提下,通过HotSpot虚拟机的Instrument功能动态加入原本并不存在的调试代码。在插件页面,选择BTrace Workbench,然后安装我们创建一段输出固定内容的死循环并运行package cc.lisen.test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * description: * * @author: lisen * @Blog:<a href="https://lisen.cc">lisen.cc</a> * @DateTime: 2025-04-20 20:34 */ public class BTraceTest { public String sayHello(String name) { return "Hello " + name; } public static void main(String[] args) throws IOException { while (true) { BTraceTest bTraceTest = new BTraceTest(); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.println(bTraceTest.sayHello(reader.readLine())); } } }打开VisualVM,找到我们的程序,点击右键选择【Trace Application】,然后输入以下脚本/* BTrace Script Template */ import org.openjdk.btrace.core.annotations.*; import static org.openjdk.btrace.core.BTraceUtils.*; @BTrace(unsafe=true) public class TracingScript { @OnMethod( clazz="cc.lisen.test.BTraceTest", method="sayHello", location=@Location(Kind.RETURN) ) public static void func(@Self cc.lisen.test.BTraceTest instance,String test,@Return String result) { System.out.println("调用堆栈:"); jstack(); } }
2025年04月20日
12 阅读
0 评论
0 点赞
2025-04-20
jhsdb:基于服务性代理的调试工具
JHSDB是一款基于服务性代理实现的进程外调试工具。服务性代理是HotSpot虚拟机中一组用于映射Java虚拟机运行信息的API集合。通过服务性代理,可以在一个独立的Java虚拟机进程里分析其他HotSpot虚拟机的内部数据,或者从HotSpot虚拟机进程内存中dump出来的转出快照里还原出虚拟机当时的运行状态细节。下面我们通过实际代码演示一下JHSDB常见的使用命令。壹、准备演示代码public class JHSDB_TestCase { static class ObjectHolder { } static class Test { static ObjectHolder staticObjectHolder = new ObjectHolder(); ObjectHolder instanceObjectHolder = new ObjectHolder(); void foo() { ObjectHolder localObjectHolder = new ObjectHolder(); System.out.println("done"); } } public static void main(String[] args) { Test test = new Test(); test.foo(); } }贰、查看VMID为了查看虚拟机信息,我们需要先知道虚拟机的进程ID,我们通过下面的命令查看VMIDjps -l叁、jhsdb常见使用命令一、hsdbjhsdb hsdb命令用于打开可视化可视化监控页面jhsdb hsdb命令格式如下jhsdb hsdb <pid>打开可视化界面jhsdb hsdb --pid 19825 二、jmapjmap命令用于生成堆转储快照,与JDK自带的jmap命令类似。jhsdb jmap命令格式如下jhsdb jmap [--pid pid | --exe executable --core coredump] [options]其中options包括--heap to print java heap summary //显示Java堆详细信息--binaryheap to dump java heap in hprof binary format--dumpfile name of the dump file //导出 Java 虚拟机堆的快照,生成文件--histo to print histogram of java object heap //打印 Java 对象堆的直方图--clstats to print class loader statistics //打印 Java 堆的类加载器统计信息--finalizerinfo to print information on objects awaiting finalization //打印有关等待完成的对象的信息查看堆栈信息jhsdb jmap --pid 14383 --heap转储堆栈快照除了查看堆栈信息,我们也可以转储堆栈信息。jhsdb jmap --pid 14383 --binaryheap --dumpfile /users/lisen/downloads/heap.bin查看class信息可以查看instances(实例数)、bytes(大小)、class name(类名)等信息jhsdb jmap --pid 14383 --histo 三、jinfojinfo命令用于实时查看和修改虚拟机各项参数。jhsdb jmap命令格式如下jhsdb jinfo <option> <pid>其中option包括以下内容flags:查看所有虚拟机参数sysprops:查看Java系统属性值输出虚拟机参数jhsdb jinfo --pid 14383 --flags输出Java系统属性jhsdb jinfo --pid 14383 --sysprops 四、jsnapjsnap打印性能计数器信息。jhsdb jsnap 命令格式如下jhsdb jnap <option> <pid>打印性能计数信息jhsdb jsnap --pid 14383 五、jstackjhsdb jstack打印线程信息jhsdb jstack命令格式如下jhsdb jstack <pid> <option>其中option包括以下内容locks:打印线程信息mixed:尝试打印Java栈与本地方法栈的信息(需操作系统支持)打印锁信息jhsdb jstack --locks --pid 14383
2025年04月20日
8 阅读
0 评论
0 点赞
2025-04-18
MacOS上平滑鼠标滚动效果的小工具
相信许多从Windows换到Mac的小伙盘,一开始的时候,肯定对于Mac的鼠标不太适应,这个不适应,我觉得大多数人都来源于下面两个方面:1.Mac下如果使用的不是Magic Mouse,其他所有的鼠标都给人一种发飘的感觉。2.鼠标滚轮页面滚动与Windows下页面随鼠标反向滚动的逻辑不同,Mac下页面滚动是与手指移动同方向的,当然可以通过设置「自然滚动」解决这个问题,但是在MacOS中这是一项全局设置,且系统当下并不支持将触控板和鼠标滚轮的滚动逻辑分开设置。这些问题只是个人长时间的使用习惯导致的,如果你长时间使用MacOS,突然换到Windows也会感到Windows的操作比较别扭。为了解决这些问题,这里给大家推荐几个MacOS上的小工具。{mtitle title="mos"/}最推荐大家使用的就是mos。一个用于在MacOS上平滑你的鼠标滚动效果的小工具, 让你的滚轮爽如触控板。而且是开源免费的工具。主要功能疯狂平滑你的鼠标滚动效果支持分离触控板/鼠标事件, 单独翻转鼠标滚动方向。滚动曲线的自定义调整。安装方法 Homebrew安装安装命令如下brew install --cask mos后续更新可以通过以下命令brew update brew reinstall mos手工安装如果手工安装,可以通过 Github页面下载最新版本安装。{mtitle title="Mac Mouse Fix"/}Mac Mouse Fix 是一款适用于 macOS 的第三方鼠标平滑滚动及鼠标按键设置工具。通过先进的滚动算法带给您流畅而灵敏的鼠标滚动体验,该算法在流动性和控制之间达到了完美的平衡。另外允许您调整鼠标的滚动方向。{message type="error" content="Mac Mouse Fix是收费的"/}主要功能流畅而灵敏的鼠标滚动。允许您独立于触控板滚动方向更改鼠标滚动方向。安装方法Homebrew安装执行以下命令安装brew install mac-mouse-fix手工安装在 官网下载最新版本安装即可。{mtitle title="Better And Better"/}Better And Better 2.0 将强大功能与优秀人机交互结合提升到一个崭新的高度。全面提升Mac触控板、鼠标、键盘使用,数百种动作手势、绘图手势与预设、脚本、快捷键完美协作,为你带来无与伦比的Mac操作体验。{message type="warning" content="可以免费使用,高级功能需要收费"/}主要功能绘画手势动作手势调节跟踪速度滚轮水平/垂直方向反转安装可以 官网下载安装包。
2025年04月18日
13 阅读
0 评论
0 点赞
2025-04-18
借助jstack分析CPU占用高及线程死锁问题
jstack是用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内存每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因。一、分析CPU占用高的问题在实际项目开发过程中,CPU占用异常是经常遇到的问题,当遇到CPU异常时,我们可以借助jstack进行问题排查。下面我们通过模拟,刨析一下整个问题分析过程。模拟代码在下面的代码中,我们写了一个死循环,模拟CPU占用过高问题。public class ThreadMain { private static ExecutorService executorService = Executors.newFixedThreadPool(5); public static void main(String[] args) { Task task1 = new Task(); Task task2 = new Task(); executorService.execute(task1); executorService.execute(task2); } public static Object lock = new Object(); static class Task implements Runnable { public void run() { synchronized (lock) { long sum = 0L; while (true) { sum += 1; } } } } }查询CPU占用高的进程在Linux服务器,我们通过top命令,可以查看CPU信息,默认按照由高到低显示。输入top命令,查询进程信息。top可以看到,目前占用最高的进程ID是9420查询对应线程在上面,我们已经查询到9420进程,我们可以进一步分析,9420进程内到底是哪个线程CPU占用最高。输入一下命令,可以查看线程信息top Hp 9420可以看到目前占用CPU最高的线程是9445线程转换我们通过top命令查询到的9445线程ID是10进制的,但是jstack展示的线程是16进制的,因此我们需要将10进制的9445转成16进制,也就是0x24e5通过jstack查询线程快照输入一下命令jstack 9420{alert type="info"}jstack后的vmid就是进程ID{/alert}jstack输出信息的nid就是16进制的线程id,我们找到nid是0x24e5线程,可以定位到具体代码位置 at ThreadMain$Task.run(ThreadMain.java:30),也就是ThreadMain.java的第30行。二、分析线程死锁问题模拟代码我们通过下面的代码,模拟线程死锁问题public class ThreadMain { private final static Object lockA = new Object(); private final static Object lockB = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "获得了锁LockA"); try { Thread.sleep(1000); } catch (InterruptedException e) { try { throw new RuntimeException(e); } catch (RuntimeException ex) { throw new RuntimeException(ex); } } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "获得了锁LockB"); } } }).start(); new Thread(() -> { synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "获得了锁LockB"); try { Thread.sleep(1000); } catch (InterruptedException e) { try { throw new RuntimeException(e); } catch (RuntimeException ex) { throw new RuntimeException(ex); } } synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "获得了锁LockA"); } } }).start(); } }查询VMID我们通过jps命令,可以查询虚拟机进程IDjps -l可以看到,我们当前VMID是18395查询线程信息同样通过jstack命令可以查询进程信息。jstack 18395重点关注状态时BLOCKED的线程,我们可以看到Thread-1跟Thread-1目前都是BLOCKED状态,Thread-1目前锁定了<0x0000000782539600>并且正在等待<0x00000007825395f0>,而Thread-0正好相反,Thread-0锁定了<0x00000007825395f0>并且正在等待<0x0000000782539600>,这就导致两个线程相互锁定。当然,我们也可以看到两个线程异常发生的位置。
2025年04月18日
12 阅读
0 评论
0 点赞
1
2
...
53